All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.staticanalysis.AddSerialVersionUidToSerializable Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2024 the original author or authors.
 * 

* Licensed under the Moderne Source Available License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* https://docs.moderne.io/licensing/moderne-source-available-license *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.openrewrite.staticanalysis; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.Space; import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.marker.Markers; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; public class AddSerialVersionUidToSerializable extends Recipe { @Override public String getDisplayName() { return "Add `serialVersionUID` to a `Serializable` class when missing"; } @Override public String getDescription() { return "A `serialVersionUID` field is strongly recommended in all `Serializable` classes. If this is not " + "defined on a `Serializable` class, the compiler will generate this value. If a change is later made " + "to the class, the generated value will change and attempts to deserialize the class will fail."; } @Override public Set getTags() { return Collections.singleton("RSPEC-S2057"); } @Override public TreeVisitor getVisitor() { return new JavaIsoVisitor() { final JavaTemplate template = JavaTemplate.builder("private static final long serialVersionUID = 1;").build(); @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { // Anonymous classes are not of interest return method; } @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { // Anonymous classes are not of interest return multiVariable; } @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx); if (c.getKind() != J.ClassDeclaration.Kind.Type.Class || !requiresSerialVersionField(classDecl.getType())) { return c; } AtomicBoolean needsSerialVersionId = new AtomicBoolean(true); J.Block body = c.getBody(); c = c.withBody(c.getBody().withStatements(ListUtils.map(c.getBody().getStatements(), s -> { if (!(s instanceof J.VariableDeclarations)) { return s; } J.VariableDeclarations varDecls = (J.VariableDeclarations) s; for (J.VariableDeclarations.NamedVariable v : varDecls.getVariables()) { if ("serialVersionUID".equals(v.getSimpleName())) { needsSerialVersionId.set(false); return maybeAutoFormat(varDecls, maybeFixVariableDeclarations(varDecls), ctx, new Cursor(getCursor(), body)); } } return s; }))); if (needsSerialVersionId.get()) { c = template.apply(updateCursor(c), c.getBody().getCoordinates().firstStatement()); } return c; } private J.VariableDeclarations maybeFixVariableDeclarations(J.VariableDeclarations varDecls) { List modifiers = varDecls.getModifiers(); if (!J.Modifier.hasModifier(modifiers, J.Modifier.Type.Private) || !J.Modifier.hasModifier(modifiers, J.Modifier.Type.Static) || !J.Modifier.hasModifier(modifiers, J.Modifier.Type.Final)) { varDecls = varDecls.withModifiers(Arrays.asList( new J.Modifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, J.Modifier.Type.Private, Collections.emptyList()), new J.Modifier(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, null, J.Modifier.Type.Static, Collections.emptyList()), new J.Modifier(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, null, J.Modifier.Type.Final, Collections.emptyList()) )); } if (TypeUtils.asPrimitive(varDecls.getType()) != JavaType.Primitive.Long) { varDecls = varDecls.withTypeExpression(new J.Primitive(Tree.randomId(), Space.EMPTY, Markers.EMPTY, JavaType.Primitive.Long)); } return varDecls; } private boolean requiresSerialVersionField(@Nullable JavaType type) { if (type == null) { return false; } else if (type instanceof JavaType.Primitive) { return true; } else if (type instanceof JavaType.Array) { return requiresSerialVersionField(((JavaType.Array) type).getElemType()); } else if (type instanceof JavaType.Parameterized) { JavaType.Parameterized parameterized = (JavaType.Parameterized) type; if (parameterized.isAssignableTo("java.util.Collection") || parameterized.isAssignableTo("java.util.Map")) { //If the type is either a collection or a map, make sure the type parameters are serializable. We //force all type parameters to be checked to correctly scoop up all non-serializable candidates. boolean typeParametersSerializable = true; for (JavaType typeParameter : parameterized.getTypeParameters()) { typeParametersSerializable = typeParametersSerializable && requiresSerialVersionField(typeParameter); } return typeParametersSerializable; } //All other parameterized types fall through } else if (type instanceof JavaType.FullyQualified) { JavaType.FullyQualified fq = (JavaType.FullyQualified) type; if (fq.getKind() == JavaType.Class.Kind.Enum) { return false; } if (fq.getKind() != JavaType.Class.Kind.Interface && !fq.isAssignableTo("java.lang.Throwable")) { return fq.isAssignableTo("java.io.Serializable"); } } return false; } }; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy