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 ball.tools.javac.AbstractTaskListener;
023import com.sun.source.util.TaskEvent;
024import java.io.ObjectStreamClass;
025import java.io.Serializable;
026import java.lang.reflect.Field;
027import java.util.Comparator;
028import java.util.Iterator;
029import java.util.Set;
030import java.util.TreeSet;
031import javax.annotation.processing.Processor;
032import javax.annotation.processing.RoundEnvironment;
033import javax.lang.model.element.Element;
034import javax.lang.model.element.TypeElement;
035import javax.lang.model.element.VariableElement;
036import lombok.NoArgsConstructor;
037import lombok.ToString;
038
039import static javax.lang.model.element.ElementKind.CLASS;
040import static javax.lang.model.util.ElementFilter.fieldsIn;
041import static javax.tools.Diagnostic.Kind.WARNING;
042
043/**
044 * {@link Processor} implementation to check subclasses of
045 * {@link Serializable} to verify a {@code serialVersionUID} field has been
046 * defined.
047 *
048 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
049 */
050@ServiceProviderFor({ Processor.class })
051@ForElementKinds({ CLASS })
052@ForSubclassesOf(Serializable.class)
053@NoArgsConstructor @ToString
054public class SerializableProcessor extends AnnotatedNoAnnotationProcessor {
055    private static abstract class PROTOTYPE {
056        private static final long serialVersionUID = 0L;
057    }
058
059    private static final Field PROTOTYPE = PROTOTYPE.class.getDeclaredFields()[0];
060
061    static { PROTOTYPE.setAccessible(true); }
062
063    private final Set<TypeElement> set = new TreeSet<>(Comparator.comparing(t -> t.getQualifiedName().toString()));
064
065    @Override
066    protected void whenAnnotationProcessingFinished() {
067        javac.addTaskListener(new TaskListenerImpl());
068    }
069
070    @Override
071    protected void process(RoundEnvironment roundEnv, Element element) {
072        if (! isGenerated(element)) {
073            TypeElement type = (TypeElement) element;
074            VariableElement field =
075                fieldsIn(type.getEnclosedElements()).stream()
076                .filter(t -> t.getSimpleName().contentEquals(PROTOTYPE.getName()))
077                .findFirst().orElse(null);
078
079            if (! (field != null
080                   && field.getModifiers().containsAll(getModifiers(PROTOTYPE))
081                   && isAssignableTo(PROTOTYPE.getType()).test(field))) {
082                set.add(type);
083            }
084        }
085    }
086
087    @NoArgsConstructor @ToString
088    private class TaskListenerImpl extends AbstractTaskListener {
089        @Override
090        public void finished(TaskEvent event) {
091            switch (event.getKind()) {
092            case GENERATE:
093                ClassLoader loader = getClassPathClassLoader(fm);
094                Iterator<TypeElement> iterator = set.iterator();
095
096                while (iterator.hasNext()) {
097                    TypeElement type = iterator.next();
098                    String name = elements.getBinaryName(type).toString();
099
100                    try {
101                        Class<?> cls = Class.forName(name, true, loader);
102                        long uid = ObjectStreamClass.lookup(cls).getSerialVersionUID();
103
104                        print(WARNING, type,
105                              "%s %s has no definition of %s\n%s = %dL;",
106                              getForSubclassesOf().getSimpleName(), type.getKind(), PROTOTYPE.getName(),
107                              declaration(PROTOTYPE), uid);
108
109                        iterator.remove();
110                    } catch (ClassNotFoundException exception) {
111                        continue;
112                    } catch (NoClassDefFoundError error) {
113                        continue;
114                    } catch (Throwable throwable) {
115                        continue;
116                    }
117                }
118                break;
119
120            default:
121                break;
122            }
123        }
124    }
125}