/* * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. * * Sun gratefully acknowledges that this software was originally authored * and developed by Kenneth Bradley Russell and Christopher John Kline. */ package net.java.games.gluegen; import java.io.*; import java.util.*; import java.util.regex.*; import net.java.games.gluegen.cgram.types.*; /** Parses and provides access to the contents of .cfg files for the JavaEmitter. */ public class JavaConfiguration { /* possible PrimitiveArrayExpansionMode states */ private int ALL_POINTERS = 1; private int NON_VOID_ONLY = 2; private int NO_POINTERS = 3; /* Default primitive array expansion mode is ALL_POINTERS, meaning all C pointers, including void pointers, will have Java primitive array expansions */ private int primitiveArrayExpansionMode = ALL_POINTERS; /* Determines which primitive array types void * C signatures are expanded into (assuming PrimitiveArrayExpansionMode = ALL_POINTERS). */ private boolean voidPointerExpansionToBoolean = true; private boolean voidPointerExpansionToChar = true; private boolean voidPointerExpansionToByte = true; private boolean voidPointerExpansionToShort = true; private boolean voidPointerExpansionToInt = true; private boolean voidPointerExpansionToLong = true; private boolean voidPointerExpansionToFloat = true; private boolean voidPointerExpansionToDouble = true; private int nestedReads; private String packageName; private String implPackageName; private String className; private String implClassName; /** * Root directory for the hierarchy of generated java classes. Default is * working directory. */ private String javaOutputDir = "."; /** * Directory into which generated native JNI code will be written. Default * is current working directory. */ private String nativeOutputDir = "."; /** * If true, then each native *.c and *.h file will be generated in the * directory nativeOutputDir/packageAsPath(packageName). Default is false. */ private boolean nativeOutputUsesJavaHierarchy; /** * Style of code emission. Can emit everything into one class * (AllStatic), separate interface and implementing classes * (InterfaceAndImpl), only the interface (InterfaceOnly), or only * the implementation (ImplOnly). */ private int emissionStyle = JavaEmitter.ALL_STATIC; /** * List of imports to emit at the head of the output files. */ private List/*<String>*/ imports = new ArrayList(); /** * The kind of exception raised by the generated code if run-time * checks fail. Defaults to RuntimeException. */ private String runtimeExceptionType = "RuntimeException"; private Map/*<String,TypeInfo>*/ typeInfoMap = new HashMap(); private Set/*<String>*/ returnsString = new HashSet(); private Map/*<String, String>*/ returnedArrayLengths = new HashMap(); /** * Key is function that has some byte[] arguments that should be * converted to String args; value is List of Integer argument indices */ private Map/*<String,List<Integer>>*/ argumentsAreString = new HashMap(); private Set/*<Pattern>*/ ignores = new HashSet(); private Set/*<Pattern>*/ ignoreNots = new HashSet(); private Set/*<Pattern>*/ unimplemented = new HashSet(); private Set/*<String>*/ nioDirectOnly = new HashSet(); /** See {@link #nioMode} */ public static final int NIO_MODE_VOID_ONLY = 1; /** See {@link #nioMode} */ public static final int NIO_MODE_ALL_POINTERS = 2; private int nioMode = NIO_MODE_VOID_ONLY; private Set/*<String>*/ noNio = new HashSet(); private Set/*<String>*/ forcedNio = new HashSet(); private boolean flattenNIOVariants = true; private Set/*<String>*/ manuallyImplement = new HashSet(); private Map/*<String,List<String>>*/ customJavaCode = new HashMap(); private Map/*<String,List<String>>*/ classJavadoc = new HashMap(); private Map/*<String,String>*/ structPackages = new HashMap(); private List/*<String>*/ customCCode = new ArrayList(); private List/*<String>*/ forcedStructs = new ArrayList(); private Map/*<String,List<Integer>>*/ mirroredArgs = new HashMap(); private Map/*<String, String>*/ returnValueCapacities = new HashMap(); private Map/*<String, String>*/ returnValueLengths = new HashMap(); private Map/*<String, List<String>>*/ temporaryCVariableDeclarations = new HashMap(); private Map/*<String, List<String>>*/ temporaryCVariableAssignments = new HashMap(); private Map/*<String,List<String>>*/ extendedInterfaces = new HashMap(); private Map/*<String,List<String>>*/ implementedInterfaces = new HashMap(); private Map/*<String,String>*/ javaTypeRenames = new HashMap(); /** Reads the configuration file. @param filename path to file that should be read */ public final void read(String filename) throws IOException { read(filename, null); } /** Reads the specified file, treating each line as if it started with the specified string. @param filename path to file that should be read @param linePrefix if not null, treat each line read as if it were prefixed with the specified string. */ protected final void read(String filename, String linePrefix) throws IOException { File file = new File(filename); BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(file)); } catch (FileNotFoundException fnfe) { throw new RuntimeException("Could not read file \"" + file + "\"", fnfe); } int lineNo = 0; String line = null; boolean hasPrefix = linePrefix != null && linePrefix.length() > 0; try { ++nestedReads; while ((line = reader.readLine()) != null) { ++lineNo; if (hasPrefix) { line = linePrefix + " " + line; } if (line.trim().startsWith("#")) { // comment line continue; } StringTokenizer tok = new StringTokenizer(line); if (tok.hasMoreTokens()) { // always reset delimiters in case of CustomJavaCode, etc. String cmd = tok.nextToken(" \t\n\r\f"); dispatch(cmd, tok, file, filename, lineNo); } } reader.close(); } finally { --nestedReads; } if (nestedReads == 0) { if (allStatic() && implClassName != null) { throw new IllegalStateException( "Error in configuration file \"" + filename + "\": Cannot use " + "directive \"ImplJavaClass\" in conjunction with " + "\"Style AllStatic\""); } if (className == null) { throw new RuntimeException("Output class name was not specified in configuration file"); } if (packageName == null) { throw new RuntimeException("Output package name was not specified in configuration file"); } if (allStatic()) { implClassName = className; // If we're using the "Style AllStatic" directive, then the // implPackageName is the same as the regular package name implPackageName = packageName; } else { if (implClassName == null) { // implClassName defaults to "<className>Impl" if ImplJavaClass // directive is not used implClassName = className + "Impl"; } if (implPackageName == null) { // implPackageName defaults to "<packageName>.impl" if ImplPackage // directive is not used implPackageName = packageName + ".impl"; } } } } /** Returns the package name parsed from the configuration file. */ public String packageName() { return packageName; } /** Returns the implementation package name parsed from the configuration file. */ public String implPackageName() { return implPackageName; } /** Returns the class name parsed from the configuration file. */ public String className() { return className; } /** Returns the implementation class name parsed from the configuration file. */ public String implClassName() { return implClassName; } /** Returns the Java code output directory parsed from the configuration file. */ public String javaOutputDir() { return javaOutputDir; } /** Returns the native code output directory parsed from the configuration file. */ public String nativeOutputDir() { return nativeOutputDir; } /** Returns whether the native code directory structure mirrors the Java hierarchy. */ public boolean nativeOutputUsesJavaHierarchy() { return nativeOutputUsesJavaHierarchy; } /** Returns the code emission style (constants in JavaEmitter) parsed from the configuration file. */ public int emissionStyle() { return emissionStyle; } /** Returns the kind of exception to raise if run-time checks fail in the generated code. */ public String runtimeExceptionType() { return runtimeExceptionType; } /** Returns the list of imports that should be emitted at the top of each .java file. */ public List/*<String>*/ imports() { return imports; } /* Return Primitive Array Expansion Mode state */ public boolean isPrimArrayExpModeAllPtrs() { return (primitiveArrayExpansionMode == ALL_POINTERS); } public boolean isPrimArrayExpModeNonVoidPtrs() { return (primitiveArrayExpansionMode == NON_VOID_ONLY); } public boolean isPrimArrayExpModeNoPtrs() { return (primitiveArrayExpansionMode == NO_POINTERS); } /** Returns VoidPointerExpansion values */ public boolean voidPointerExpansionToBoolean() { return voidPointerExpansionToBoolean; } public boolean voidPointerExpansionToChar() { return voidPointerExpansionToChar; } public boolean voidPointerExpansionToByte() { return voidPointerExpansionToByte; } public boolean voidPointerExpansionToShort() { return voidPointerExpansionToShort; } public boolean voidPointerExpansionToInt() { return voidPointerExpansionToInt; } public boolean voidPointerExpansionToLong() { return voidPointerExpansionToLong; } public boolean voidPointerExpansionToFloat() { return voidPointerExpansionToFloat; } public boolean voidPointerExpansionToDouble() { return voidPointerExpansionToDouble; } /** If this type should be considered opaque, returns the TypeInfo describing the replacement type. Returns null if this type should not be considered opaque. */ public TypeInfo typeInfo(Type type, TypeDictionary typedefDictionary) { // Because typedefs of pointer types can show up at any point, // walk the pointer chain looking for a typedef name that is in // the TypeInfo map. int pointerDepth = type.pointerDepth(); for (int i = 0; i <= pointerDepth; i++) { String name = type.getName(); if (name != null) { TypeInfo info = (TypeInfo) typeInfoMap.get(name); while (info != null) { if (info.name().equals(name) && info.pointerDepth() == i) { return info; } info = info.next(); } } if (type.isCompound()) { // Try struct name as well name = type.asCompound().getStructName(); if (name != null) { TypeInfo info = (TypeInfo) typeInfoMap.get(name); while (info != null) { if (info.name().equals(name) && info.pointerDepth() == i) { return info; } info = info.next(); } } } // Try all typedef names that map to this type Set entrySet = typedefDictionary.entrySet(); for (Iterator iter = entrySet.iterator(); iter.hasNext(); ) { Map.Entry entry = (Map.Entry) iter.next(); // "eq" equality is OK to use here since all types have been canonicalized if (entry.getValue() == type) { name = (String) entry.getKey(); TypeInfo info = (TypeInfo) typeInfoMap.get(name); while (info != null) { if (info.name().equals(name) && info.pointerDepth() == i) { return info; } info = info.next(); } } } if (type.isPointer()) { type = type.asPointer().getTargetType(); } } return null; } /** Indicates whether the given function (which returns a <code>char*</code> in C) should be translated as returning a <code>java.lang.String</code>. */ public boolean returnsString(String functionName) { return returnsString.contains(functionName); } /** Provides a Java MessageFormat expression indicating the number of elements in the returned array from the specified function name. Indicates that the given return value, which must be a pointer to a CompoundType, is actually an array of the CompoundType rather than a pointer to a single object. */ public String returnedArrayLength(String functionName) { return (String) returnedArrayLengths.get(functionName); } /** Returns a list of <code>Integer</code>s which are the indices of <code>const char*</code> arguments that should be converted to <code>String</code>s. Returns null if there are no such hints for the given function name. */ public List/*<Integer>*/ stringArguments(String functionName) { return (List) argumentsAreString.get(functionName); } /** Returns true if the given function should only create a java.nio variant, and no array variants, for <code>void*</code> and other C primitive pointers. */ public boolean nioDirectOnly(String functionName) { return nioDirectOnly.contains(functionName); } /** Returns true if the user requested that the given function should only create array variants, and no java.nio variant, for <code>void*</code> and other C primitive pointers, overriding the NIO mode default. */ public boolean noNio(String functionName) { return noNio.contains(functionName); } /** Returns true if the user requested that the given function should create a java.nio variant for the given function's <code>void*</code> and other C primitive pointers, overriding the NIO mode default. */ public boolean forcedNio(String functionName) { return forcedNio.contains(functionName); } /** Returns the default NIO generation mode for C primitive pointer arguments. NIO_MODE_VOID_ONLY is the default and specifies that only void* arguments will have java.nio variants generated for them. NIO_MODE_ALL_POINTERS specifies that all C primitive arguments will have java.nio variants generated. */ public int nioMode() { return nioMode; } /** Returns true if, for the plethora of java.nio variants generated for primitive C pointer types, the emitter should flatten the output down to two variants: one taking only Java primitive arrays as arguments, and one taking only java.nio.Buffers as arguments. */ public boolean flattenNIOVariants() { return flattenNIOVariants; } /** Returns true if the glue code for the given function will be manually implemented by the end user. */ public boolean manuallyImplement(String functionName) { return manuallyImplement.contains(functionName); } /** Returns a list of Strings containing user-implemented code for the given Java type name (not fully-qualified, only the class name); returns either null or an empty list if there is no custom code for the class. */ public List customJavaCodeForClass(String className) { List res = (List) customJavaCode.get(className); if (res == null) { res = new ArrayList(); customJavaCode.put(className, res); } return res; } /** Returns a list of Strings containing Javadoc documentation for the given Java type name (not fully-qualified, only the class name); returns either null or an empty list if there is no Javadoc documentation for the class. */ public List javadocForClass(String className) { List res = (List) classJavadoc.get(className); if (res == null) { res = new ArrayList(); classJavadoc.put(className, res); } return res; } /** Returns the package into which to place the glue code for accessing the specified struct. Defaults to emitting into the regular package (i.e., the result of {@link #packageName}). */ public String packageForStruct(String structName) { String res = (String) structPackages.get(structName); if (res == null) { res = packageName; } return res; } /** Returns, as a List of Strings, the custom C code to be emitted along with the glue code for the main class. */ public List/*<String>*/ customCCode() { return customCCode; } /** Returns, as a List of Strings, the structs for which glue code emission should be forced. */ public List/*<String>*/ forcedStructs() { return forcedStructs; } /** Returns a List of Integers indicating the indices of arguments in this function that should be expanded to the same type when binding functions with multiple void* arguments. Returns null if no such indices were specified. */ public List/*<Integer>*/ mirroredArgs(String functionName) { return (List) mirroredArgs.get(functionName); } /** Returns a MessageFormat string of the C expression calculating the capacity of the java.nio.ByteBuffer being returned from a native method, or null if no expression has been specified. */ public String returnValueCapacity(String functionName) { return (String) returnValueCapacities.get(functionName); } /** Returns a MessageFormat string of the C expression calculating the length of the array being returned from a native method, or null if no expression has been specified. */ public String returnValueLength(String functionName) { return (String) returnValueLengths.get(functionName); } /** Returns a List of Strings of expressions declaring temporary C variables in the glue code for the specified function. */ public List/*<String>*/ temporaryCVariableDeclarations(String functionName) { return (List) temporaryCVariableDeclarations.get(functionName); } /** Returns a List of Strings of expressions containing assignments to temporary C variables in the glue code for the specified function. */ public List/*<String>*/ temporaryCVariableAssignments(String functionName) { return (List) temporaryCVariableAssignments.get(functionName); } /** Returns a List of Strings indicating the interfaces the passed interface should declare it extends. May return null or a list of zero length if there are none. */ public List/*<String>*/ extendedInterfaces(String interfaceName) { List res = (List) extendedInterfaces.get(interfaceName); if (res == null) { res = new ArrayList(); extendedInterfaces.put(interfaceName, res); } return res; } /** Returns a List of Strings indicating the interfaces the passed class should declare it implements. May return null or a list of zero length if there are none. */ public List/*<String>*/ implementedInterfaces(String className) { List res = (List) implementedInterfaces.get(className); if (res == null) { res = new ArrayList(); implementedInterfaces.put(className, res); } return res; } /** Returns true if this #define, function, struct, or field within a struct should be ignored during glue code generation. */ public boolean shouldIgnore(String symbol) { //System.err.println("CHECKING IGNORE: " + symbol); // Simple case; the entire symbol is in the ignore table. if (ignores.contains(symbol)) { return true; } // Ok, the slow case. We need to check the entire table, in case the table // contains an regular expression that matches the symbol. for (Iterator iter = ignores.iterator(); iter.hasNext(); ) { Pattern regexp = (Pattern)iter.next(); Matcher matcher = regexp.matcher(symbol); if (matcher.matches()) { return true; } } // Check negated ignore table if not empty if (ignoreNots.size() > 0) { // Ok, the slow case. We need to check the entire table, in case the table // contains an regular expression that matches the symbol. for (Iterator iter = ignoreNots.iterator(); iter.hasNext(); ) { Pattern regexp = (Pattern)iter.next(); Matcher matcher = regexp.matcher(symbol); if (!matcher.matches()) { return true; } } } return false; } /** Returns true if this function should be given a body which throws a run-time exception with an "unimplemented" message during glue code generation. */ public boolean isUnimplemented(String symbol) { // Simple case; the entire symbol is in the ignore table. if (unimplemented.contains(symbol)) { return true; } // Ok, the slow case. We need to check the entire table, in case the table // contains an regular expression that matches the symbol. for (Iterator iter = unimplemented.iterator(); iter.hasNext(); ) { Pattern regexp = (Pattern)iter.next(); Matcher matcher = regexp.matcher(symbol); if (matcher.matches()) { return true; } } return false; } /** Returns a replacement name for this type, which should be the name of a Java wrapper class for a C struct, or the name unchanged if no RenameJavaType directive was specified for this type. */ public String renameJavaType(String javaTypeName) { String rename = (String) javaTypeRenames.get(javaTypeName); if (rename != null) { return rename; } return javaTypeName; } /** Returns true if the emission style is AllStatic. */ public boolean allStatic() { return (emissionStyle == JavaEmitter.ALL_STATIC); } /** Returns true if an interface should be emitted during glue code generation. */ public boolean emitInterface() { return (emissionStyle() == JavaEmitter.INTERFACE_AND_IMPL || emissionStyle() == JavaEmitter.INTERFACE_ONLY); } /** Returns true if an implementing class should be emitted during glue code generation. */ public boolean emitImpl() { return (emissionStyle() == JavaEmitter.ALL_STATIC || emissionStyle() == JavaEmitter.INTERFACE_AND_IMPL || emissionStyle() == JavaEmitter.IMPL_ONLY); } //---------------------------------------------------------------------- // Internals only below this point // protected void dispatch(String cmd, StringTokenizer tok, File file, String filename, int lineNo) throws IOException { //System.err.println("read cmd = [" + cmd + "]"); if (cmd.equalsIgnoreCase("Package")) { packageName = readString("package", tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("ImplPackage")) { implPackageName = readString("ImplPackage", tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("JavaClass")) { className = readString("JavaClass", tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("ImplJavaClass")) { implClassName = readString("ImplJavaClass", tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("JavaOutputDir")) { javaOutputDir = readString("JavaOutputDir", tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("NativeOutputDir")) { nativeOutputDir = readString("NativeOutputDir", tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("HierarchicalNativeOutput")) { String tmp = readString("HierarchicalNativeOutput", tok, filename, lineNo); nativeOutputUsesJavaHierarchy = Boolean.valueOf(tmp).booleanValue(); } else if (cmd.equalsIgnoreCase("Style")) { String style = readString("Style", tok, filename, lineNo); if (style.equalsIgnoreCase("AllStatic")) { emissionStyle = JavaEmitter.ALL_STATIC; } else if (style.equalsIgnoreCase("InterfaceAndImpl")) { emissionStyle = JavaEmitter.INTERFACE_AND_IMPL; } else if (style.equalsIgnoreCase("InterfaceOnly")) { emissionStyle = JavaEmitter.INTERFACE_ONLY; } else if (style.equalsIgnoreCase("ImplOnly")) { emissionStyle = JavaEmitter.IMPL_ONLY; } else { System.err.println("WARNING: Error parsing \"style\" command at line " + lineNo + " in file \"" + filename + "\""); } } else if (cmd.equalsIgnoreCase("Import")) { imports.add(readString("Import", tok, filename, lineNo)); } else if (cmd.equalsIgnoreCase("Opaque")) { readOpaque(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("ReturnsString")) { readReturnsString(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("ReturnedArrayLength")) { readReturnedArrayLength(tok, filename, lineNo); // Warning: make sure delimiters are reset at the top of this loop // because ReturnedArrayLength changes them. } else if (cmd.equalsIgnoreCase("ArgumentIsString")) { readArgumentIsString(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("Ignore")) { readIgnore(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("IgnoreNot")) { readIgnoreNot(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("Unimplemented")) { readUnimplemented(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("IgnoreField")) { readIgnoreField(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("ManuallyImplement")) { readManuallyImplement(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("CustomJavaCode")) { readCustomJavaCode(tok, filename, lineNo); // Warning: make sure delimiters are reset at the top of this loop // because readCustomJavaCode changes them. } else if (cmd.equalsIgnoreCase("CustomCCode")) { readCustomCCode(tok, filename, lineNo); // Warning: make sure delimiters are reset at the top of this loop // because readCustomCCode changes them. } else if (cmd.equalsIgnoreCase("ClassJavadoc")) { readClassJavadoc(tok, filename, lineNo); // Warning: make sure delimiters are reset at the top of this loop // because readClassJavadoc changes them. } else if (cmd.equalsIgnoreCase("NioDirectOnly")) { nioDirectOnly.add(readString("NioDirectOnly", tok, filename, lineNo)); } else if (cmd.equalsIgnoreCase("NoNio")) { noNio.add(readString("NoNio", tok, filename, lineNo)); } else if (cmd.equalsIgnoreCase("ForcedNio")) { forcedNio.add(readString("ForcedNio", tok, filename, lineNo)); } else if (cmd.equalsIgnoreCase("NioMode")) { readNioMode(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("FlattenNIOVariants")) { flattenNIOVariants = readBoolean("FlattenNIOVariants", tok, filename, lineNo).booleanValue(); } else if (cmd.equalsIgnoreCase("PrimitiveArrayExpansionMode")) { readPrimitiveExpMode(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("VoidPointerExpansion")) { readVoidPointerExpansionSet(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("EmitStruct")) { forcedStructs.add(readString("EmitStruct", tok, filename, lineNo)); } else if (cmd.equalsIgnoreCase("MirrorExpandedBindingArgs")) { readMirrorExpandedBindingArgs(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("StructPackage")) { readStructPackage(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("TemporaryCVariableDeclaration")) { readTemporaryCVariableDeclaration(tok, filename, lineNo); // Warning: make sure delimiters are reset at the top of this loop // because TemporaryCVariableDeclaration changes them. } else if (cmd.equalsIgnoreCase("TemporaryCVariableAssignment")) { readTemporaryCVariableAssignment(tok, filename, lineNo); // Warning: make sure delimiters are reset at the top of this loop // because TemporaryCVariableAssignment changes them. } else if (cmd.equalsIgnoreCase("ReturnValueCapacity")) { readReturnValueCapacity(tok, filename, lineNo); // Warning: make sure delimiters are reset at the top of this loop // because ReturnValueCapacity changes them. } else if (cmd.equalsIgnoreCase("ReturnValueLength")) { readReturnValueLength(tok, filename, lineNo); // Warning: make sure delimiters are reset at the top of this loop // because ReturnValueLength changes them. } else if (cmd.equalsIgnoreCase("Include")) { doInclude(tok, file, filename, lineNo); } else if (cmd.equalsIgnoreCase("IncludeAs")) { doIncludeAs(tok, file, filename, lineNo); } else if (cmd.equalsIgnoreCase("Extends")) { readExtend(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("Implements")) { readImplements(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("RenameJavaType")) { readRenameJavaType(tok, filename, lineNo); } else if (cmd.equalsIgnoreCase("RuntimeExceptionType")) { runtimeExceptionType = readString("RuntimeExceptionType", tok, filename, lineNo); } else { throw new RuntimeException("Unknown command \"" + cmd + "\" in command file " + filename + " at line number " + lineNo); } } protected String readString(String cmd, StringTokenizer tok, String filename, int lineNo) { try { return tok.nextToken(); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"" + cmd + "\" command at line " + lineNo + " in file \"" + filename + "\": missing expected parameter", e); } } protected Boolean readBoolean(String cmd, StringTokenizer tok, String filename, int lineNo) { try { return Boolean.valueOf(tok.nextToken()); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"" + cmd + "\" command at line " + lineNo + " in file \"" + filename + "\": missing expected boolean value", e); } } protected Class stringToPrimitiveType(String type) throws ClassNotFoundException { if (type.equals("boolean")) return Boolean.TYPE; if (type.equals("byte")) return Integer.TYPE; if (type.equals("char")) return Character.TYPE; if (type.equals("short")) return Short.TYPE; if (type.equals("int")) return Integer.TYPE; if (type.equals("long")) return Long.TYPE; if (type.equals("float")) return Float.TYPE; if (type.equals("double")) return Double.TYPE; throw new RuntimeException("Only primitive types are supported here"); } protected void readOpaque(StringTokenizer tok, String filename, int lineNo) { try { JavaType javaType = JavaType.createForClass(stringToPrimitiveType(tok.nextToken())); String cType = null; while (tok.hasMoreTokens()) { if (cType == null) { cType = tok.nextToken(); } else { cType = cType + " " + tok.nextToken(); } } if (cType == null) { throw new RuntimeException("No C type for \"Opaque\" command at line " + lineNo + " in file \"" + filename + "\""); } TypeInfo info = parseTypeInfo(cType, javaType); addTypeInfo(info); } catch (Exception e) { throw new RuntimeException("Error parsing \"Opaque\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readReturnsString(StringTokenizer tok, String filename, int lineNo) { try { String name = tok.nextToken(); returnsString.add(name); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"ReturnsString\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readReturnedArrayLength(StringTokenizer tok, String filename, int lineNo) { try { String functionName = tok.nextToken(); String restOfLine = tok.nextToken("\n\r\f"); restOfLine = restOfLine.trim(); returnedArrayLengths.put(functionName, restOfLine); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"ReturnedArrayLength\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readIgnore(StringTokenizer tok, String filename, int lineNo) { try { String regex = tok.nextToken(); ignores.add(Pattern.compile(regex)); //System.err.println("IGNORING " + regex + " / " + ignores.get(regex)); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"Ignore\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readIgnoreNot(StringTokenizer tok, String filename, int lineNo) { try { String regex = tok.nextToken(); ignoreNots.add(Pattern.compile(regex)); //System.err.println("IGNORING NEGATION OF " + regex + " / " + ignores.get(regex)); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"IgnoreNot\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readUnimplemented(StringTokenizer tok, String filename, int lineNo) { try { String regex = tok.nextToken(); unimplemented.add(Pattern.compile(regex)); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"Unimplemented\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readIgnoreField(StringTokenizer tok, String filename, int lineNo) { try { String containingStruct = tok.nextToken(); String name = tok.nextToken(); ignores.add(Pattern.compile(containingStruct + " " + name)); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"IgnoreField\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readManuallyImplement(StringTokenizer tok, String filename, int lineNo) { try { String name = tok.nextToken(); manuallyImplement.add(name); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"ManuallyImplement\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readCustomJavaCode(StringTokenizer tok, String filename, int lineNo) { try { String className = tok.nextToken(); String restOfLine = tok.nextToken("\n\r\f"); addCustomJavaCode(className, restOfLine); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"CustomJavaCode\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void addCustomJavaCode(String className, String code) { List codeList = customJavaCodeForClass(className); codeList.add(code); } protected void readCustomCCode(StringTokenizer tok, String filename, int lineNo) { try { String restOfLine = tok.nextToken("\n\r\f"); customCCode.add(restOfLine); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"CustomCCode\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readClassJavadoc(StringTokenizer tok, String filename, int lineNo) { try { String className = tok.nextToken(); String restOfLine = tok.nextToken("\n\r\f"); addClassJavadoc(className, restOfLine); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"ClassJavadoc\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void addClassJavadoc(String className, String code) { List codeList = javadocForClass(className); codeList.add(code); } /** * Sets the default NIO generation mode for C primitive * pointers. Options are VOID_ONLY or ALL_POINTERS. When the mode is * set to VOID_ONLY, java.nio variants of methods are only generated * for C primitive pointers of type <code>void*</code>. All other * pointers are translated by default into Java primitive arrays. * When the mode is set to ALL_POINTERS, C primitive pointers of * other types (i.e., <code>int*</code>) will have java.nio variants * generated for them (i.e., <code>IntBuffer</code> as opposed to * merely <code>int[]</code>). This default mode can be overridden * with the NioDirectOnly and NoNio directives. The default for this mode * is currently VOID_ONLY. */ protected void readNioMode(StringTokenizer tok, String filename, int lineNo) { try { String mode = tok.nextToken(); if (mode.equalsIgnoreCase("VOID_ONLY")) { nioMode = NIO_MODE_VOID_ONLY; } else if (mode.equalsIgnoreCase("ALL_POINTERS")) { nioMode = NIO_MODE_ALL_POINTERS; } else { throw new RuntimeException("Error parsing \"NioMode\" command at line " + lineNo + " in file \"" + filename + "\"; expected VOID_ONLY or ALL_POINTERS"); } } catch (NoSuchElementException e) { throw new RuntimeException( "Error parsing \"NioMode\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } /** * Sets the Primitive Array Expansion Mode. Options are ALL_POINTERS (default), * NON_VOID_ONLY, and NO_POINTERS. ALL_POINTERS means that all Primitive array * C pointers (float*, int*, void*, etc.) get expanded into non-NIO buffer * targets. The setting has no bearing on whether NIO buffer targets happen. * So float * goes to float[], int* goes to int[], and void* gets expanded to * (by default) double[], float[], long[], int[], short[], byte[], char[], and * boolean[]. NON_VOID_ONLY means that all Primitive array pointers except * for void * get expanded, and NO_POINTERS means that no C Primitive array * pointers get expanded. * For void * expansion the default is all 8 primitive types as listed above. * However, the types can be restricted by use of the VoidPointerExpansion * attribute defined elsewhere in this file. */ protected void readPrimitiveExpMode(StringTokenizer tok, String filename, int lineNo) { try { String mode = tok.nextToken(); if (mode.equalsIgnoreCase("ALL_POINTERS")) { primitiveArrayExpansionMode = ALL_POINTERS; } else if (mode.equalsIgnoreCase("NON_VOID_ONLY")) { primitiveArrayExpansionMode = NON_VOID_ONLY; } else if (mode.equalsIgnoreCase("NO_POINTERS")) { primitiveArrayExpansionMode = NO_POINTERS; } else { throw new RuntimeException("Error parsing \"PrimitiveArrayExpansionMode\" command at line " + lineNo + " in file \"" + filename + "\"; expected NO_POINTERS, NON_VOID_ONLY or ALL_POINTERS"); } } catch (NoSuchElementException e) { throw new RuntimeException( "Error parsing \"PrimitiveArrayExpansionMode\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } /** * readVoidPointerExpansionSet: * Parses VoidPointerExpansion arguments. Expecting subset of boolean, char, byte, * short, int, long,float, double. Example grammar file syntax: * VoidPointerExpansion short int float byte double * If PrimitiveArrayExpansionMode is set to NON_VOID_ONLY or NO_POINTERS, then * the VoidPointerExpansion attribute has no effect, since in that case void * pointers are not expanded. */ protected void readVoidPointerExpansionSet(StringTokenizer tok, String filename, int lineNo) { int number_passes=0; boolean finished = false; voidPointerExpansionToBoolean = false; voidPointerExpansionToChar = false; voidPointerExpansionToByte = false; voidPointerExpansionToShort = false; voidPointerExpansionToInt = false; voidPointerExpansionToLong = false; voidPointerExpansionToFloat = false; voidPointerExpansionToDouble = false; while(!finished) { try { String mode = tok.nextToken(); if (mode.equalsIgnoreCase("float")) { voidPointerExpansionToFloat = true; number_passes++; } else if (mode.equalsIgnoreCase("int")) { voidPointerExpansionToInt = true; number_passes++; } else if (mode.equalsIgnoreCase("byte")) { voidPointerExpansionToByte = true; number_passes++; } else if (mode.equalsIgnoreCase("short")) { voidPointerExpansionToShort = true; number_passes++; } else if (mode.equalsIgnoreCase("boolean")) { voidPointerExpansionToBoolean = true; number_passes++; } else if (mode.equalsIgnoreCase("char")) { voidPointerExpansionToChar = true; number_passes++; } else if (mode.equalsIgnoreCase("long")) { voidPointerExpansionToLong = true; number_passes++; } else if (mode.equalsIgnoreCase("double")) { voidPointerExpansionToDouble = true; number_passes++; } else { throw new RuntimeException("Error parsing \"VoidPointerExpansion\" command at line " + lineNo + " in file \"" + filename + "\"; expected some combination of boolean, char, byte, short, int, long, float, double"); } } catch (NoSuchElementException e) { if(number_passes == 0) { throw new RuntimeException( "Error parsing \"VoidPointerExpansion\" command at line " + lineNo + " in file \"" + filename + "\"", e); } finished = true; } } } /** * When void* arguments in the C function prototypes are encountered, the * emitter will try to expand the binding and create Java entry points for * all possible array types. If there are 2 or more void* arguments in the C * prototype, this directive lets you specify which of those arguments * should always be expanded to the same type. <p> * * For example, given the C prototype: * <pre> * void FuncName(void *foo, void *bar); * </pre> * * The emitter will normally emit multiple Java entry points: * <pre> * public abstract void FuncName(boolean[] foo, java.nio.Buffer bar); * public abstract void FuncName(boolean[] foo, boolean[] bar); * public abstract void FuncName(boolean[] foo, byte[] bar); * public abstract void FuncName(boolean[] foo, char[] bar); * public abstract void FuncName(boolean[] foo, short[] bar); * public abstract void FuncName(boolean[] foo, int[] bar); * public abstract void FuncName(boolean[] foo, long[] bar); * public abstract void FuncName(boolean[] foo, float[] bar); * public abstract void FuncName(boolean[] foo, double[] bar); * * public abstract void FuncName(byte[] foo, java.nio.Buffer bar); * public abstract void FuncName(byte[] foo, boolean[] bar); * public abstract void FuncName(byte[] foo, byte[] bar); * <...etc for all variants on the second parameter...> * * public abstract void FuncName(char[] foo, java.nio.Buffer bar); * public abstract void FuncName(char[] foo, boolean[] bar); * public abstract void FuncName(char[] foo, byte[] bar); * <...etc for all variants on the second parameter...> * <...and so on for all remaining variants on the first parameter...> * </pre> * * This directive lets you specify that arguments at a particular index * should always be expanded to the same type. For example, the directive: * <pre> * MirrorExpandedBindingArgs FuncName 0 1 * </pre> * will force the first and second arguments in function FuncName to be * expanded identically. This would result in the emission of the following * entry points only: * <pre> * public abstract void FuncName(java.nio.Buffer[] foo, java.nio.Buffer bar); * public abstract void FuncName(boolean[] foo, boolean[] bar); * public abstract void FuncName(byte[] foo, byte[] bar); * public abstract void FuncName(char[] foo, char[] bar); * public abstract void FuncName(short[] foo, short[] bar); * public abstract void FuncName(int[] foo, int[] bar); * public abstract void FuncName(long[] foo, long[] bar); * public abstract void FuncName(float[] foo, float[] bar); * public abstract void FuncName(double[] foo, double[] bar); * </pre> */ protected void readMirrorExpandedBindingArgs(StringTokenizer tok, String filename, int lineNo) { try { String methodName = tok.nextToken(); ArrayList argIndices = new ArrayList(2); while (tok.hasMoreTokens()) { Integer idx = Integer.valueOf(tok.nextToken()); argIndices.add(idx); } if(argIndices.size() > 1) { mirroredArgs.put(methodName, argIndices); } else { throw new RuntimeException("ERROR: Error parsing \"MirrorExpandedBindingArgs\" command at line " + lineNo + " in file \"" + filename + "\": directive requires at least 2 argument indices"); } } catch (NoSuchElementException e) { throw new RuntimeException( "Error parsing \"MirrorExpandedBindingArgs\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } /** * When const char* arguments in the C function prototypes are encountered, * the emitter will normally convert them to <code>byte[]</code> * arguments. This directive lets you specify which of those arguments * should be converted to <code>String</code> arguments instead of <code> * byte[] </code>. <p> * * For example, given the C prototype: * <pre> * void FuncName(const char* ugh, int bar, const char *foo, const char* goop); * </pre> * * The emitter will normally emit: * <pre> * public abstract void FuncName(byte[] ugh, int bar, byte[] foo, byte[] goop); * </pre> * * However, if you supplied the following directive: * * <pre> * ArgumentIsString FuncName 0 2 * </pre> * * The emitter will instead emit: * <pre> * public abstract void FuncName(String ugh, int bar, String foo, byte[] goop); * </pre> * */ protected void readArgumentIsString(StringTokenizer tok, String filename, int lineNo) { try { String methodName = tok.nextToken(); ArrayList argIndices = new ArrayList(2); while (tok.hasMoreTokens()) { Integer idx = Integer.valueOf(tok.nextToken()); argIndices.add(idx); } if(argIndices.size() > 0) { argumentsAreString.put(methodName, argIndices); } else { throw new RuntimeException("ERROR: Error parsing \"ArgumentIsString\" command at line " + lineNo + " in file \"" + filename + "\": directive requires specification of at least 1 index"); } } catch (NoSuchElementException e) { throw new RuntimeException( "Error parsing \"ArgumentIsString\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readStructPackage(StringTokenizer tok, String filename, int lineNo) { try { String struct = tok.nextToken(); String pkg = tok.nextToken(); structPackages.put(struct, pkg); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"StructPackage\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readReturnValueCapacity(StringTokenizer tok, String filename, int lineNo) { try { String functionName = tok.nextToken(); String restOfLine = tok.nextToken("\n\r\f"); restOfLine = restOfLine.trim(); returnValueCapacities.put(functionName, restOfLine); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"ReturnValueCapacity\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readReturnValueLength(StringTokenizer tok, String filename, int lineNo) { try { String functionName = tok.nextToken(); String restOfLine = tok.nextToken("\n\r\f"); restOfLine = restOfLine.trim(); returnValueLengths.put(functionName, restOfLine); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"ReturnValueLength\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readTemporaryCVariableDeclaration(StringTokenizer tok, String filename, int lineNo) { try { String functionName = tok.nextToken(); String restOfLine = tok.nextToken("\n\r\f"); restOfLine = restOfLine.trim(); List list = (List) temporaryCVariableDeclarations.get(functionName); if (list == null) { list = new ArrayList/*<String>*/(); temporaryCVariableDeclarations.put(functionName, list); } list.add(restOfLine); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"TemporaryCVariableDeclaration\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readTemporaryCVariableAssignment(StringTokenizer tok, String filename, int lineNo) { try { String functionName = tok.nextToken(); String restOfLine = tok.nextToken("\n\r\f"); restOfLine = restOfLine.trim(); List list = (List) temporaryCVariableAssignments.get(functionName); if (list == null) { list = new ArrayList/*<String>*/(); temporaryCVariableAssignments.put(functionName, list); } list.add(restOfLine); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"TemporaryCVariableAssignment\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void doInclude(StringTokenizer tok, File file, String filename, int lineNo) throws IOException { try { String includedFilename = tok.nextToken(); File includedFile = new File(includedFilename); if (!includedFile.isAbsolute()) { includedFile = new File(file.getParentFile(), includedFilename); } read(includedFile.getAbsolutePath()); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"Include\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void doIncludeAs(StringTokenizer tok, File file, String filename, int lineNo) throws IOException { try { StringBuffer linePrefix = new StringBuffer(128); while (tok.countTokens() > 1) { linePrefix.append(tok.nextToken()); linePrefix.append(" "); } // last token is filename String includedFilename = tok.nextToken(); File includedFile = new File(includedFilename); if (!includedFile.isAbsolute()) { includedFile = new File(file.getParentFile(), includedFilename); } read(includedFile.getAbsolutePath(), linePrefix.toString()); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"IncludeAs\" command at line " + lineNo + " in file \"" + filename + "\"", e); } } protected void readExtend(StringTokenizer tok, String filename, int lineNo) { try { String interfaceName = tok.nextToken(); List intfs = extendedInterfaces(interfaceName); intfs.add(tok.nextToken()); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"Extends\" command at line " + lineNo + " in file \"" + filename + "\": missing expected parameter", e); } } protected void readImplements(StringTokenizer tok, String filename, int lineNo) { try { String className = tok.nextToken(); List intfs = implementedInterfaces(className); intfs.add(tok.nextToken()); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"Implements\" command at line " + lineNo + " in file \"" + filename + "\": missing expected parameter", e); } } protected void readRenameJavaType(StringTokenizer tok, String filename, int lineNo) { try { String fromName = tok.nextToken(); String toName = tok.nextToken(); javaTypeRenames.put(fromName, toName); } catch (NoSuchElementException e) { throw new RuntimeException("Error parsing \"RenameJavaType\" command at line " + lineNo + " in file \"" + filename + "\": missing expected parameter", e); } } protected static TypeInfo parseTypeInfo(String cType, JavaType javaType) { String typeName = null; int pointerDepth = 0; int idx = 0; while (idx < cType.length() && (cType.charAt(idx) != ' ') && (cType.charAt(idx) != '*')) { ++idx; } typeName = cType.substring(0, idx); // Count pointer depth while (idx < cType.length()) { if (cType.charAt(idx) == '*') { ++pointerDepth; } ++idx; } return new TypeInfo(typeName, pointerDepth, javaType); } protected void addTypeInfo(TypeInfo info) { TypeInfo tmp = (TypeInfo) typeInfoMap.get(info.name()); if (tmp == null) { typeInfoMap.put(info.name(), info); return; } while (tmp.next() != null) { tmp = tmp.next(); } tmp.setNext(info); } }