1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.jnigen.parsing; 18 19 import com.github.javaparser.JavaParser; 20 import com.github.javaparser.ast.CompilationUnit; 21 import com.github.javaparser.ast.body.BodyDeclaration; 22 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; 23 import com.github.javaparser.ast.body.EnumDeclaration; 24 import com.github.javaparser.ast.body.MethodDeclaration; 25 import com.github.javaparser.ast.body.ModifierSet; 26 import com.github.javaparser.ast.body.Parameter; 27 import com.github.javaparser.ast.body.TypeDeclaration; 28 29 import java.io.ByteArrayInputStream; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.Comparator; 33 import java.util.HashMap; 34 import java.util.Iterator; 35 import java.util.Map; 36 import java.util.Stack; 37 38 public class RobustJavaMethodParser implements JavaMethodParser { 39 private static final String JNI_MANUAL = "MANUAL"; 40 private static final Map<String, ArgumentType> plainOldDataTypes; 41 private static final Map<String, ArgumentType> arrayTypes; 42 private static final Map<String, ArgumentType> bufferTypes; 43 44 static { 45 plainOldDataTypes = new HashMap<String, ArgumentType>(); 46 plainOldDataTypes.put("boolean", ArgumentType.Boolean); 47 plainOldDataTypes.put("byte", ArgumentType.Byte); 48 plainOldDataTypes.put("char", ArgumentType.Char); 49 plainOldDataTypes.put("short", ArgumentType.Short); 50 plainOldDataTypes.put("int", ArgumentType.Integer); 51 plainOldDataTypes.put("long", ArgumentType.Long); 52 plainOldDataTypes.put("float", ArgumentType.Float); 53 plainOldDataTypes.put("double", ArgumentType.Double); 54 55 arrayTypes = new HashMap<String, ArgumentType>(); 56 arrayTypes.put("boolean", ArgumentType.BooleanArray); 57 arrayTypes.put("byte", ArgumentType.ByteArray); 58 arrayTypes.put("char", ArgumentType.CharArray); 59 arrayTypes.put("short", ArgumentType.ShortArray); 60 arrayTypes.put("int", ArgumentType.IntegerArray); 61 arrayTypes.put("long", ArgumentType.LongArray); 62 arrayTypes.put("float", ArgumentType.FloatArray); 63 arrayTypes.put("double", ArgumentType.DoubleArray); 64 65 bufferTypes = new HashMap<String, ArgumentType>(); 66 bufferTypes.put("Buffer", ArgumentType.Buffer); 67 bufferTypes.put("ByteBuffer", ArgumentType.ByteBuffer); 68 bufferTypes.put("CharBuffer", ArgumentType.CharBuffer); 69 bufferTypes.put("ShortBuffer", ArgumentType.ShortBuffer); 70 bufferTypes.put("IntBuffer", ArgumentType.IntBuffer); 71 bufferTypes.put("LongBuffer", ArgumentType.LongBuffer); 72 bufferTypes.put("FloatBuffer", ArgumentType.FloatBuffer); 73 bufferTypes.put("DoubleBuffer", ArgumentType.DoubleBuffer); 74 } 75 76 Stack<TypeDeclaration> classStack = new Stack<TypeDeclaration>(); 77 78 @Override 79 public ArrayList<JavaSegment> parse (String classFile) throws Exception { 80 CompilationUnit unit = JavaParser.parse(new ByteArrayInputStream(classFile.getBytes())); 81 ArrayList<JavaMethod> methods = new ArrayList<JavaMethod>(); 82 getJavaMethods(methods, getOuterClass(unit)); 83 ArrayList<JniSection> methodBodies = getNativeCodeBodies(classFile); 84 ArrayList<JniSection> sections = getJniSections(classFile); 85 alignMethodBodies(methods, methodBodies); 86 ArrayList<JavaSegment> segments = sortMethodsAndSections(methods, sections); 87 return segments; 88 } 89 90 private ArrayList<JavaSegment> sortMethodsAndSections (ArrayList<JavaMethod> methods, ArrayList<JniSection> sections) { 91 ArrayList<JavaSegment> segments = new ArrayList<JavaSegment>(); 92 segments.addAll(methods); 93 segments.addAll(sections); 94 Collections.sort(segments, new Comparator<JavaSegment>() { 95 @Override 96 public int compare (JavaSegment o1, JavaSegment o2) { 97 return o1.getStartIndex() - o2.getStartIndex(); 98 } 99 }); 100 return segments; 101 } 102 103 private void alignMethodBodies (ArrayList<JavaMethod> methods, ArrayList<JniSection> methodBodies) { 104 for (JavaMethod method : methods) { 105 for (JniSection section : methodBodies) { 106 if (method.getEndIndex() == section.getStartIndex()) { 107 if (section.getNativeCode().startsWith(JNI_MANUAL)) { 108 section.setNativeCode(section.getNativeCode().substring(JNI_MANUAL.length())); 109 method.setManual(true); 110 } 111 method.setNativeCode(section.getNativeCode()); 112 break; 113 } 114 } 115 } 116 } 117 118 private void getJavaMethods (ArrayList<JavaMethod> methods, TypeDeclaration type) { 119 classStack.push(type); 120 if (type.getMembers() != null) { 121 for (BodyDeclaration member : type.getMembers()) { 122 if (member instanceof ClassOrInterfaceDeclaration || member instanceof EnumDeclaration) { 123 getJavaMethods(methods, (TypeDeclaration)member); 124 } else { 125 if (member instanceof MethodDeclaration) { 126 MethodDeclaration method = (MethodDeclaration)member; 127 if (!ModifierSet.hasModifier(((MethodDeclaration)member).getModifiers(), ModifierSet.NATIVE)) continue; 128 methods.add(createMethod(method)); 129 } 130 } 131 } 132 } 133 classStack.pop(); 134 } 135 136 private JavaMethod createMethod (MethodDeclaration method) { 137 String className = classStack.peek().getName(); 138 String name = method.getName(); 139 boolean isStatic = ModifierSet.hasModifier(method.getModifiers(), ModifierSet.STATIC); 140 String returnType = method.getType().toString(); 141 ArrayList<Argument> arguments = new ArrayList<Argument>(); 142 143 if (method.getParameters() != null) { 144 for (Parameter parameter : method.getParameters()) { 145 arguments.add(new Argument(getArgumentType(parameter), parameter.getId().getName())); 146 } 147 } 148 149 return new JavaMethod(className, name, isStatic, returnType, null, arguments, method.getBeginLine(), method.getEndLine()); 150 } 151 152 private ArgumentType getArgumentType (Parameter parameter) { 153 String[] typeTokens = parameter.getType().toString().split("\\."); 154 String type = typeTokens[typeTokens.length - 1]; 155 int arrayDim = 0; 156 for (int i = 0; i < type.length(); i++) { 157 if (type.charAt(i) == '[') arrayDim++; 158 } 159 type = type.replace("[", "").replace("]", ""); 160 161 if (arrayDim >= 1) { 162 if (arrayDim > 1) return ArgumentType.ObjectArray; 163 ArgumentType arrayType = arrayTypes.get(type); 164 if (arrayType == null) { 165 return ArgumentType.ObjectArray; 166 } 167 return arrayType; 168 } 169 170 if (plainOldDataTypes.containsKey(type)) return plainOldDataTypes.get(type); 171 if (bufferTypes.containsKey(type)) return bufferTypes.get(type); 172 if (type.equals("String")) return ArgumentType.String; 173 return ArgumentType.Object; 174 } 175 176 private TypeDeclaration getOuterClass (CompilationUnit unit) { 177 for (TypeDeclaration type : unit.getTypes()) { 178 if (type instanceof ClassOrInterfaceDeclaration || type instanceof EnumDeclaration) return type; 179 } 180 throw new RuntimeException("Couldn't find class, is your java file empty?"); 181 } 182 183 private ArrayList<JniSection> getJniSections (String classFile) { 184 ArrayList<JniSection> sections = getComments(classFile); 185 Iterator<JniSection> iter = sections.iterator(); 186 while (iter.hasNext()) { 187 JniSection section = iter.next(); 188 if (!section.getNativeCode().startsWith("JNI")) { 189 iter.remove(); 190 } else { 191 section.setNativeCode(section.getNativeCode().substring(3)); 192 } 193 } 194 return sections; 195 } 196 197 private ArrayList<JniSection> getNativeCodeBodies (String classFile) { 198 ArrayList<JniSection> sections = getComments(classFile); 199 Iterator<JniSection> iter = sections.iterator(); 200 while (iter.hasNext()) { 201 JniSection section = iter.next(); 202 if (section.getNativeCode().startsWith("JNI")) iter.remove(); 203 if (section.getNativeCode().startsWith("-{")) iter.remove(); 204 } 205 return sections; 206 } 207 208 private ArrayList<JniSection> getComments (String classFile) { 209 ArrayList<JniSection> sections = new ArrayList<JniSection>(); 210 211 boolean inComment = false; 212 int start = 0; 213 int startLine = 0; 214 int line = 1; 215 for (int i = 0; i < classFile.length() - 2; i++) { 216 char c1 = classFile.charAt(i); 217 char c2 = classFile.charAt(i + 1); 218 char c3 = classFile.charAt(i + 2); 219 if (c1 == '\n') line++; 220 if (!inComment) { 221 if (c1 == '/' && c2 == '*' && c3 != '*') { 222 inComment = true; 223 start = i; 224 startLine = line; 225 } 226 } else { 227 if (c1 == '*' && c2 == '/') { 228 sections.add(new JniSection(classFile.substring(start + 2, i), startLine, line)); 229 inComment = false; 230 } 231 } 232 } 233 234 return sections; 235 } 236 } 237