001package ball.annotation.processing; 002/*- 003 * ########################################################################## 004 * Utilities 005 * %% 006 * Copyright (C) 2008 - 2022 Allen D. Ball 007 * %% 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 * ########################################################################## 020 */ 021import ball.annotation.ServiceProviderFor; 022import java.lang.reflect.Method; 023import java.util.List; 024import javax.annotation.processing.ProcessingEnvironment; 025import javax.annotation.processing.Processor; 026import javax.annotation.processing.RoundEnvironment; 027import javax.lang.model.element.Element; 028import javax.lang.model.element.ExecutableElement; 029import javax.lang.model.element.TypeElement; 030import javax.lang.model.type.TypeMirror; 031import lombok.NoArgsConstructor; 032import lombok.ToString; 033 034import static java.util.stream.Collectors.toList; 035import static javax.lang.model.element.ElementKind.METHOD; 036import static javax.lang.model.element.Modifier.PRIVATE; 037import static javax.lang.model.element.Modifier.STATIC; 038import static javax.tools.Diagnostic.Kind.ERROR; 039import static javax.tools.Diagnostic.Kind.WARNING; 040 041/** 042 * {@link Processor} implementation to check {@link Object#clone()} 043 * implementations to verify: 044 * <ol> 045 * <li value="1"> 046 * The implementing {@link Class} also implements {@link Cloneable} 047 * </li> 048 * <li value="2"> 049 * The implementation throws {@link CloneNotSupportedException} (unless 050 * some "intravening" superclass' implementation does not) 051 * </li> 052 * <li value="3"> 053 * The implementation returns a subtype of the implementation type 054 * </li> 055 * </ol> 056 * 057 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 058 */ 059@ServiceProviderFor({ Processor.class }) 060@ForElementKinds({ METHOD }) 061@NoArgsConstructor @ToString 062public class ObjectCloneProcessor extends AnnotatedNoAnnotationProcessor { 063 private static final Method PROTOTYPE; 064 065 static { 066 try { 067 PROTOTYPE = Object.class.getDeclaredMethod("clone"); 068 PROTOTYPE.setAccessible(true); 069 } catch (Exception exception) { 070 throw new ExceptionInInitializerError(exception); 071 } 072 } 073 074 private ExecutableElement METHOD = null; 075 private TypeElement CLONEABLE = null; 076 077 @Override 078 public void init(ProcessingEnvironment processingEnv) { 079 super.init(processingEnv); 080 081 try { 082 METHOD = asExecutableElement(PROTOTYPE); 083 CLONEABLE = asTypeElement(Cloneable.class); 084 085 criteria.add(t -> overrides((ExecutableElement) t, METHOD)); 086 } catch (Exception exception) { 087 print(ERROR, exception); 088 } 089 } 090 091 @Override 092 protected void process(RoundEnvironment roundEnv, Element element) { 093 if (! isGenerated(element)) { 094 ExecutableElement method = (ExecutableElement) element; 095 TypeElement type = (TypeElement) method.getEnclosingElement(); 096 097 if (! type.getInterfaces().contains(CLONEABLE.asType())) { 098 print(WARNING, type, 099 "%s overrides '%s' but does not implement %s", 100 type.getKind(), declaration(PROTOTYPE), CLONEABLE.getSimpleName()); 101 } 102 103 if (! types.isAssignable(method.getReturnType(), type.asType())) { 104 print(WARNING, method, 105 "%s overrides '%s' but does not return a subclass of %s", 106 method.getKind(), declaration(PROTOTYPE), type.getSimpleName()); 107 } 108 109 List<TypeMirror> throwables = METHOD.getThrownTypes().stream().collect(toList()); 110 111 throwables.retainAll(overrides(method).getThrownTypes()); 112 throwables.removeAll(method.getThrownTypes()); 113 throwables.stream() 114 .map(t -> types.asElement(t)) 115 .map(t -> t.getSimpleName()) 116 .forEach(t -> print(WARNING, method, 117 "%s overrides '%s' but does not throw %s", 118 method.getKind(), declaration(PROTOTYPE), t)); 119 } 120 } 121}