Home | History | Annotate | Download | only in parsing
      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