/* * Copyright (C) 2015 Jian Chen <jian@mobmead.com>. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.mobmead.easympermission.javac.handlers; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; import lombok.core.AnnotationValues; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; import lombok.javac.handlers.JavacHandlerUtil; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.tree.JCTree.*; import com.mobmead.easympermission.Permission; import com.mobmead.easympermission.RuntimePermission; import static com.mobmead.easympermission.javac.fluent.Builder.*; import static lombok.javac.Javac.*; import static lombok.javac.handlers.JavacHandlerUtil.*; /** * Handles the {@code easympermission.RuntimePermission} annotation for javac. */ @ProviderFor(JavacAnnotationHandler.class) public class HandleRuntimePermission extends JavacAnnotationHandler<RuntimePermission> { private final String MESSAGE_PERMISSIONS_WERE_NOT_GRANTED = "Permissions were not granted."; private final String MESSAGE_PERMISSIONS_HAVE_BEEN_GRANTED = "Permissions have been granted."; private final int FIRST_PERMISSION_REQUEST_CODE = 1000; private final String IS_PERMISSION_GRANTED_METHOD = "isPermissionGranted$"; @Override public void handle(AnnotationValues<RuntimePermission> annotation, JCAnnotation ast, JavacNode annotationNode) { deleteAnnotationIfNeccessary(annotationNode, RuntimePermission.class); JavacNode typeNode = annotationNode.up(); boolean notAClass = !isClass(typeNode); if (notAClass) { annotationNode.addError("@RuntimePermission is only supported on a class."); return; } JCMethodDecl method; List<PermissionAnnotatedItem> permissionList = findAllAnnotatedMethods(typeNode); for (PermissionAnnotatedItem item : permissionList) { method = createPermissionCheckedMethod(typeNode, item); removeMethod(typeNode, item.getMethod()); injectMethod(typeNode, recursiveSetGeneratedBy(method, annotationNode.get(), typeNode.getContext())); } method = recursiveSetGeneratedBy(createIsPermissionGrantedMethod(typeNode), annotationNode.get(), typeNode.getContext()); injectMethod(typeNode, method); method = recursiveSetGeneratedBy(createOnRequestPermissionMethod(typeNode, permissionList), annotationNode.get(), typeNode.getContext()); injectMethod(typeNode, method); } private JCMethodDecl createOnRequestPermissionMethod(JavacNode typeNode, List<PermissionAnnotatedItem> permissionList) { JavacTreeMaker treeMaker = typeNode.getTreeMaker(); ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); JCTree.JCExpression returnType = treeMaker.Type(Javac.createVoidType(treeMaker, CTC_VOID)); List<JCVariableDecl> paramList = List.of( Variable(typeNode) .modifiers(Flags.PARAMETER) .name("requestCode") .type(treeMaker.TypeIdent(CTC_INT)) .value(null) .build(), Variable(typeNode) .modifiers(Flags.PARAMETER) .name("permissions") .type(treeMaker.TypeArray(genJavaLangTypeRef(typeNode, "String"))) .value(null) .build(), Variable(typeNode) .modifiers(Flags.PARAMETER) .name("grantResults") .type(treeMaker.TypeArray(treeMaker.TypeIdent(CTC_INT))) .value(null) .build() ); for (PermissionAnnotatedItem item : permissionList) { JCReturn jcReturn = treeMaker.Return(null); JCBinary cmpRequestCode = treeMaker.Binary(CTC_EQUAL, treeMaker.Ident(typeNode.toName("requestCode")), treeMaker.Literal(CTC_INT, item.getRequestCode())); JCBinary cmpGrantResult = treeMaker.Binary(CTC_NOT_EQUAL, treeMaker.Indexed(treeMaker.Ident(typeNode.toName("grantResults")), treeMaker.Literal(CTC_INT, 0)), JavacHandlerUtil.chainDots(typeNode, "android", "content", "pm", "PackageManager", "PERMISSION_GRANTED")); JCExpression toastMethod = JavacHandlerUtil.chainDots(typeNode, "android", "widget", "Toast", "makeText"); List<JCExpression> toastArgs = List.<JCTree.JCExpression>of( treeMaker.Ident(typeNode.toName("this")), treeMaker.Literal(MESSAGE_PERMISSIONS_WERE_NOT_GRANTED), treeMaker.Literal(CTC_INT, 1)); JCMethodInvocation printlnInvocation = treeMaker.Apply(List.<JCTree.JCExpression>nil(), toastMethod, toastArgs); JCExpressionStatement thenPart = treeMaker.Exec( treeMaker.Apply(List.<JCTree.JCExpression>nil(), treeMaker.Select(printlnInvocation, typeNode.toName("show")), List.<JCExpression>nil())); toastArgs = List.<JCTree.JCExpression>of( treeMaker.Ident(typeNode.toName("this")), treeMaker.Literal(MESSAGE_PERMISSIONS_HAVE_BEEN_GRANTED), treeMaker.Literal(CTC_INT, 1)); printlnInvocation = treeMaker.Apply(List.<JCTree.JCExpression>nil(), toastMethod, toastArgs); JCExpressionStatement elsePart = treeMaker.Exec( treeMaker.Apply(List.<JCTree.JCExpression>nil(), treeMaker.Select(printlnInvocation, typeNode.toName("show")), List.<JCExpression>nil())); JCIf jcIfGrantResult = If(typeNode) .condition(cmpGrantResult) .withThen(Block(typeNode).last(thenPart).build()) .withElse(Block(typeNode).last(elsePart).build()) .build(); JCIf jcIfRequestCode = If(typeNode) .condition(cmpRequestCode) .onlyThen(Block(typeNode).add(jcIfGrantResult).last(jcReturn).build()) .build(); statements.append(jcIfRequestCode); } JCBlock body = Block(typeNode) .last(statements.toList()) .build(); return Method(typeNode) .modifiers(Flags.PUBLIC) .returnType(returnType) .name("onRequestPermissionsResult") .paramType() .parameters(paramList) .thrown() .body(body) .build(); } private List<PermissionAnnotatedItem> findAllAnnotatedMethods(JavacNode node) { int requestCode = FIRST_PERMISSION_REQUEST_CODE; ListBuffer<PermissionAnnotatedItem> permissionList = new ListBuffer<PermissionAnnotatedItem>(); node = upToTypeNode(node); if (node != null && node.get() instanceof JCTree.JCClassDecl) { for (JCTree def : ((JCTree.JCClassDecl)node.get()).defs) { if (def instanceof JCMethodDecl) { JCMethodDecl md = (JCMethodDecl) def; List<JCAnnotation> annotations = md.getModifiers().getAnnotations(); if (annotations != null) { for (JCAnnotation anno : annotations) { if (anno.getAnnotationType() != null && anno.getAnnotationType().type.toString().equals(Permission.class.getName())) { if (anno.getArguments().get(0) instanceof JCTree.JCAssign) { JCTree.JCAssign assign = (JCTree.JCAssign) anno.getArguments().get(0); PermissionAnnotatedItem item = new PermissionAnnotatedItem(md, requestCode, convertStringToList(assign.rhs.toString())); permissionList.append(item); requestCode++; } } } } } } } return permissionList.toList(); } private List<String> convertStringToList(String input) { ListBuffer<String> list = new ListBuffer<String>(); for (String a : input.replaceAll("^\\{+", "").replaceAll("\\}$", "").split(",")) { list.append(a.replaceAll("^\"+", "").replaceAll("\"$", "")); } return list.toList(); } private JCMethodDecl createPermissionCheckedMethod(JavacNode typeNode, PermissionAnnotatedItem permissionAnnotatedItem) { JCBlock block = createPermissionCheckedBlock(typeNode, permissionAnnotatedItem); JCMethodDecl origMethod = permissionAnnotatedItem.getMethod(); return Method(typeNode) .modifiers(origMethod.getModifiers()) .returnType((JCExpression) origMethod.getReturnType()) .name(origMethod.getName().toString()) .paramType(origMethod.getTypeParameters()) .parameters(origMethod.getParameters()) .thrown(origMethod.getThrows()) .body(block) .build(); } private void removeMethod(JavacNode typeNode, JCMethodDecl method) { typeNode = upToTypeNode(typeNode); ListBuffer<JCTree> newList = new ListBuffer<JCTree>(); for (JCTree def : ((JCTree.JCClassDecl)typeNode.get()).defs) { if (!(def instanceof JCMethodDecl && def == method)) { newList.append(def); } } ((JCTree.JCClassDecl)typeNode.get()).defs = newList.toList(); } private JCMethodDecl createIsPermissionGrantedMethod(JavacNode typeNode) { JavacTreeMaker treeMaker = typeNode.getTreeMaker(); ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); statements.append(Variable(typeNode) .modifiers(0) .name("ok") .type(treeMaker.TypeIdent(CTC_BOOLEAN)) .value(treeMaker.Literal(CTC_BOOLEAN, 1)) .build()); statements.append(Variable(typeNode) .modifiers(0) .name("failed") .type(treeMaker.TypeIdent(CTC_BOOLEAN)) .value(treeMaker.Literal(CTC_BOOLEAN, 0)) .build()); JCTree.JCExpression returnType = treeMaker.TypeIdent(CTC_BOOLEAN); List<JCTypeParameter> typleList = List.nil(); List<JCVariableDecl> paramList = List.of( Variable(typeNode) .modifiers(Flags.PARAMETER) .name("permissions") .type(treeMaker.TypeArray(genJavaLangTypeRef(typeNode, "String"))) .value(null) .build() ); JCVariableDecl v = Variable(typeNode) .modifiers(Flags.PARAMETER) .name("permission") .type(genJavaLangTypeRef(typeNode, "String")) .value(null) .build(); JCExpression checkMethod = JavacHandlerUtil.chainDots(typeNode, "this", "checkSelfPermission"); List<JCExpression> checkArgs = List.<JCExpression>of(treeMaker.Ident(typeNode.toName("permission"))); JCMethodInvocation checkInvocation = treeMaker.Apply(List.<JCExpression>nil(), checkMethod, checkArgs); JCExpression cmp = treeMaker.Binary(CTC_NOT_EQUAL, checkInvocation, JavacHandlerUtil.chainDots(typeNode, "android", "content", "pm", "PackageManager", "PERMISSION_GRANTED")); JCBlock body = Block(typeNode) .last(treeMaker.Return(treeMaker.Ident(typeNode.toName("failed")))) .build(); JCIf jcIf = If(typeNode) .condition(cmp) .onlyThen(body) .build(); JCEnhancedForLoop block = treeMaker.ForeachLoop(v, treeMaker.Ident(typeNode.toName("permissions")), jcIf); statements.append(block); statements.append(treeMaker.Return(treeMaker.Ident(typeNode.toName("ok")))); body = Block(typeNode) .last(statements.toList()) .build(); return Method(typeNode) .modifiers(Flags.PRIVATE) .returnType(returnType) .name(IS_PERMISSION_GRANTED_METHOD) .paramType(typleList) .parameters(paramList) .thrown() .body(body) .build(); } private JCBlock createPermissionCheckedBlock(JavacNode typeNode, PermissionAnnotatedItem permissionAnnotatedItem) { JavacTreeMaker treeMaker = typeNode.getTreeMaker(); // Create permission list ListBuffer<JCExpression> permissionArgs = new ListBuffer<JCExpression>(); for (String permission : permissionAnnotatedItem.getPermissions()) { permissionArgs.append(treeMaker.Literal(permission)); } String permissionVarName = "_permissions_" + permissionAnnotatedItem.getMethod().getName(); JCExpression v = treeMaker.NewArray(null, List.<JCExpression>nil(), permissionArgs.toList()); JCVariableDecl permissions = Variable(typeNode) .modifiers(Flags.PRIVATE) .name(permissionVarName) .type(treeMaker.TypeArray(genJavaLangTypeRef(typeNode, "String"))) .value(v) .build(); injectField(typeNode, permissions); JCExpression checkMethod = JavacHandlerUtil.chainDots(typeNode, "this", IS_PERMISSION_GRANTED_METHOD); List<JCExpression> checkArgs = List.<JCExpression>of(treeMaker.Ident(typeNode.toName(permissionVarName))); JCMethodInvocation checkInvocation = treeMaker.Apply(List.<JCExpression>nil(), checkMethod, checkArgs); JCExpression permissionCmp = treeMaker.Binary(CTC_EQUAL, checkInvocation, treeMaker.Literal(CTC_BOOLEAN, 1)); JCExpression requestMethod = JavacHandlerUtil.chainDots(typeNode, "this", "requestPermissions"); List<JCExpression> requestArgs = List.<JCExpression>of( treeMaker.Ident(typeNode.toName(permissionVarName)), treeMaker.Literal(CTC_INT, permissionAnnotatedItem.getRequestCode())); JCMethodInvocation requestInvocation = treeMaker.Apply(List.<JCExpression>nil(), requestMethod, requestArgs); JCBlock body = Block(typeNode) .last(treeMaker.Exec(requestInvocation)) .build(); JCIf jcCheckPermissionIf = If(typeNode) .condition(permissionCmp) .withThen(permissionAnnotatedItem.getMethod().getBody()) .withElse(body) .build(); JCBlock checkPermissionBlock = Block(typeNode) .last(jcCheckPermissionIf) .build(); JCExpression versionCmp = treeMaker.Binary(CTC_LESS_THAN, JavacHandlerUtil.chainDots(typeNode, "android", "os", "Build", "VERSION", "SDK_INT"), treeMaker.Literal(CTC_INT, 23)); JCIf jcIf = If(typeNode) .condition(versionCmp) .withThen(permissionAnnotatedItem.getMethod().getBody()) .withElse(checkPermissionBlock) .build(); return Block(typeNode) .last(jcIf) .build(); } static boolean isClass(JavacNode typeNode) { return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ENUM | Flags.ANNOTATION); } static boolean isClassOrEnum(JavacNode typeNode) { return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ANNOTATION); } private class PermissionAnnotatedItem { private final int requestCode; private final List<String> permissions; private final JCMethodDecl method; public PermissionAnnotatedItem(JCMethodDecl method, int requestCode, List<String> permissions) { this.method = method; this.requestCode = requestCode; this.permissions = permissions; } public JCMethodDecl getMethod() { return method; } public List<String> getPermissions() { return permissions; } public int getRequestCode() { return requestCode; } } }