001/* 002 * Copyright (C) 2009-2014 The Project Lombok Authors. 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining a copy 005 * of this software and associated documentation files (the "Software"), to deal 006 * in the Software without restriction, including without limitation the rights 007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 008 * copies of the Software, and to permit persons to whom the Software is 009 * furnished to do so, subject to the following conditions: 010 * 011 * The above copyright notice and this permission notice shall be included in 012 * all copies or substantial portions of the Software. 013 * 014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 020 * THE SOFTWARE. 021 */ 022package hm.binkley.lombok.javac; 023 024import com.sun.tools.javac.tree.JCTree; 025import com.sun.tools.javac.tree.JCTree.JCAnnotation; 026import com.sun.tools.javac.tree.JCTree.JCAssign; 027import com.sun.tools.javac.tree.JCTree.JCBlock; 028import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; 029import com.sun.tools.javac.tree.JCTree.JCExpression; 030import com.sun.tools.javac.tree.JCTree.JCMethodDecl; 031import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; 032import com.sun.tools.javac.tree.JCTree.JCStatement; 033import com.sun.tools.javac.tree.JCTree.JCTry; 034import com.sun.tools.javac.tree.JCTree.JCVariableDecl; 035import com.sun.tools.javac.util.Context; 036import com.sun.tools.javac.util.List; 037import hm.binkley.lombok.ThreadNamed; 038import lombok.core.AnnotationValues; 039import lombok.core.HandlerPriority; 040import lombok.core.configuration.ConfigurationKey; 041import lombok.core.configuration.FlagUsageType; 042import lombok.javac.Javac; 043import lombok.javac.JavacAnnotationHandler; 044import lombok.javac.JavacNode; 045import lombok.javac.JavacTreeMaker; 046import org.kohsuke.MetaInfServices; 047 048import static com.sun.tools.javac.code.Flags.ABSTRACT; 049import static com.sun.tools.javac.code.Flags.FINAL; 050import static com.sun.tools.javac.util.List.nil; 051import static lombok.core.handlers.HandlerUtil.handleFlagUsage; 052import static lombok.javac.handlers.JavacHandlerUtil 053 .deleteAnnotationIfNeccessary; 054import static lombok.javac.handlers.JavacHandlerUtil.genJavaLangTypeRef; 055import static lombok.javac.handlers.JavacHandlerUtil.inNetbeansEditor; 056import static lombok.javac.handlers.JavacHandlerUtil.isConstructorCall; 057import static lombok.javac.handlers.JavacHandlerUtil.setGeneratedBy; 058 059/** 060 * Handles the {@link ThreadNamed} annotation for javac. 061 * 062 * @author <a href="mailto:binkley@alumni.rice.edu">B. K. Oxley (binkley)</a> 063 */ 064@MetaInfServices(JavacAnnotationHandler.class) 065@HandlerPriority(1024) 066// 2^10; @NonNull must have run first, so that we wrap around the statements 067// generated by it. 068public class HandleThreadNamed 069 extends JavacAnnotationHandler<ThreadNamed> { 070 /** 071 * lombok configuration: {@code hm.binkley.lombok.threadNamed.flagUsage} = 072 * {@code WARNING} | {@code ERROR}. 073 * <p> 074 * If set, <em>any</em> usage of {@link @ThreadNamed} results in a warning / 075 * error. 076 */ 077 public static final ConfigurationKey<FlagUsageType> THREAD_NAMED_FLAG_USAGE 078 = new ConfigurationKey<FlagUsageType>( 079 "hm.binkley.lombok.threadNamed.flagUsage", 080 "Emit a warning or error if @ThreadNamed is used.") { 081 }; 082 083 @Override 084 public void handle(final AnnotationValues<ThreadNamed> annotation, 085 final JCAnnotation ast, final JavacNode annotationNode) { 086 handleFlagUsage(annotationNode, THREAD_NAMED_FLAG_USAGE, 087 "@ThreadNamed"); 088 089 deleteAnnotationIfNeccessary(annotationNode, ThreadNamed.class); 090 if (annotation.getInstance().value().isEmpty()) { 091 annotationNode.addError("threadName cannot be the empty string."); 092 return; 093 } 094 095 final JCAnnotation anno = (JCAnnotation) annotationNode.get(); 096 final List<JCExpression> args = anno.args; 097 final JCExpression threadName = ((JCAssign) args.get(0)) 098 .getExpression(); // Must be present 099 100 final JavacNode owner = annotationNode.up(); 101 switch (owner.getKind()) { 102 case METHOD: 103 handleMethod(annotationNode, (JCMethodDecl) owner.get(), 104 threadName); 105 break; 106 default: 107 annotationNode.addError( 108 "@ThreadNamed is legal only on methods and constructors."); 109 break; 110 } 111 } 112 113 private static void handleMethod(final JavacNode annotation, 114 final JCMethodDecl method, final JCExpression threadName) { 115 final JavacNode methodNode = annotation.up(); 116 117 if (0 != (method.mods.flags & ABSTRACT)) { 118 annotation.addError( 119 "@ThreadNamed can only be used on concrete methods."); 120 return; 121 } 122 123 if (null == method.body || method.body.stats.isEmpty()) { 124 generateEmptyBlockWarning(annotation, false); 125 return; 126 } 127 128 final JCStatement constructorCall = method.body.stats.get(0); 129 final boolean isConstructorCall = isConstructorCall(constructorCall); 130 final List<JCStatement> contents = isConstructorCall 131 ? method.body.stats.tail : method.body.stats; 132 133 if (null == contents || contents.isEmpty()) { 134 generateEmptyBlockWarning(annotation, true); 135 return; 136 } 137 138 final List<JCStatement> wrapped = List 139 .of(renameThreadWhileIn(methodNode, contents, threadName, 140 method.params, annotation.get())); 141 method.body.stats = isConstructorCall ? List.of(constructorCall) 142 .appendList(wrapped) : wrapped; 143 methodNode.rebuild(); 144 } 145 146 private static void generateEmptyBlockWarning(final JavacNode annotation, 147 final boolean hasConstructorCall) { 148 if (hasConstructorCall) 149 annotation.addWarning( 150 "Calls to sibling / super constructors are always " 151 + "excluded from @ThreadNamed;" 152 + " @ThreadNamed has been ignored because there " 153 + "is no other code in " 154 + "this constructor."); 155 else 156 annotation.addWarning( 157 "This method or constructor is empty; @ThreadNamed has " 158 + "been ignored."); 159 } 160 161 private static JCStatement renameThreadWhileIn(final JavacNode node, 162 final List<JCStatement> contents, 163 final JCExpression threadNameFormat, 164 final List<JCVariableDecl> params, final JCTree source) { 165 final String currentThreadVarName = "$currentThread"; 166 final String oldThreadNameVarName = "$oldThreadName"; 167 168 final JavacTreeMaker maker = node.getTreeMaker(); 169 final Context context = node.getContext(); 170 171 final JCVariableDecl saveCurrentThread = createCurrentThreadVar(node, 172 maker, currentThreadVarName); 173 final JCVariableDecl saveOldThreadName = createOldThreadNameVar(node, 174 maker, currentThreadVarName, oldThreadNameVarName); 175 176 final JCExpression threadName = threadName(node, maker, 177 threadNameFormat, params); 178 final JCStatement changeThreadName = setThreadName(node, maker, 179 threadName, currentThreadVarName); 180 final JCStatement restoreOldThreadName = setThreadName(node, maker, 181 maker.Ident(node.toName(oldThreadNameVarName)), 182 currentThreadVarName); 183 184 final JCBlock tryBlock = setGeneratedBy(maker.Block(0, contents), 185 source, context); 186 final JCTry wrapMethod = maker.Try(tryBlock, nil(), 187 maker.Block(0, List.of(restoreOldThreadName))); 188 189 if (inNetbeansEditor(node)) { 190 //set span (start and end position) of the try statement and the 191 // main block 192 //this allows NetBeans to dive into the statement correctly: 193 final JCCompilationUnit top = (JCCompilationUnit) node.top().get(); 194 final int startPos = contents.head.pos; 195 final int endPos = Javac.getEndPosition(contents.last().pos(), top); 196 tryBlock.pos = startPos; 197 wrapMethod.pos = startPos; 198 Javac.storeEnd(tryBlock, endPos, top); 199 Javac.storeEnd(wrapMethod, endPos, top); 200 } 201 202 return setGeneratedBy(maker.Block(0, 203 List.of(saveCurrentThread, saveOldThreadName, 204 changeThreadName, wrapMethod)), source, 205 context); 206 } 207 208 private static JCVariableDecl createCurrentThreadVar(final JavacNode node, 209 final JavacTreeMaker maker, final String currentThreadVarName) { 210 return maker.VarDef(maker.Modifiers(FINAL), 211 node.toName(currentThreadVarName), 212 genJavaLangTypeRef(node, "Thread"), maker.Apply(nil(), 213 genJavaLangTypeRef(node, "Thread", "currentThread"), nil())); 214 } 215 216 private static JCVariableDecl createOldThreadNameVar(final JavacNode node, 217 final JavacTreeMaker maker, final String currentThreadVarName, 218 final String oldThreadNameVarName) { 219 return maker.VarDef(maker.Modifiers(FINAL), 220 node.toName(oldThreadNameVarName), 221 genJavaLangTypeRef(node, "String"), 222 getThreadName(node, maker, currentThreadVarName)); 223 } 224 225 private static JCExpression threadName(final JavacNode node, 226 final JavacTreeMaker maker, final JCExpression threadNameFormat, 227 final List<JCVariableDecl> params) { 228 if (params.isEmpty()) 229 return threadNameFormat; 230 231 List<JCExpression> formatArgs = List.of(threadNameFormat); 232 for (final JCVariableDecl param : params) 233 formatArgs = formatArgs.append(maker.Ident(param)); 234 235 return maker.Apply(nil(), 236 maker.Select(genJavaLangTypeRef(node, "String"), 237 node.toName("format")), formatArgs); 238 } 239 240 private static JCMethodInvocation getThreadName(final JavacNode node, 241 final JavacTreeMaker maker, final String currentThreadVarNAme) { 242 return maker.Apply(nil(), 243 maker.Select(maker.Ident(node.toName(currentThreadVarNAme)), 244 node.toName("getName")), nil()); 245 } 246 247 private static JCStatement setThreadName(final JavacNode node, 248 final JavacTreeMaker maker, final JCExpression threadName, 249 final String currentThreadVarName) { 250 return maker.Exec(maker.Apply(nil(), 251 maker.Select(maker.Ident(node.toName(currentThreadVarName)), 252 node.toName("setName")), List.of(threadName))); 253 } 254}