/* * Copyright 2015 Ben Manes. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 com.github.benmanes.caffeine.cache.node; import static com.github.benmanes.caffeine.cache.Specifications.NODE; import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS; import static com.github.benmanes.caffeine.cache.Specifications.newFieldOffset; import static com.github.benmanes.caffeine.cache.Specifications.offsetName; import static org.apache.commons.lang3.StringUtils.capitalize; import javax.lang.model.element.Modifier; import com.github.benmanes.caffeine.cache.Feature; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; /** * Adds the expiration support to the node. * * @author ben.manes@gmail.com (Ben Manes) */ public final class AddExpiration extends NodeRule { @Override protected boolean applies() { return true; } @Override protected void execute() { addVariableExpiration(); addAccessExpiration(); addWriteExpiration(); addRefreshExpiration(); } private void addVariableExpiration() { if (context.generateFeatures.contains(Feature.EXPIRE_ACCESS)) { addLink("previousInVariableOrder", "previousInAccessOrder"); addLink("nextInVariableOrder", "nextInAccessOrder"); addVariableTime("accessTime"); } else if (context.generateFeatures.contains(Feature.EXPIRE_WRITE)) { addLink("previousInVariableOrder", "previousInWriteOrder"); addLink("nextInVariableOrder", "nextInWriteOrder"); addVariableTime("writeTime"); } if (context.parentFeatures.contains(Feature.EXPIRE_ACCESS) && context.parentFeatures.contains(Feature.EXPIRE_WRITE) && context.generateFeatures.contains(Feature.REFRESH_WRITE)) { addLink("previousInVariableOrder", "previousInWriteOrder"); addLink("nextInVariableOrder", "nextInWriteOrder"); addVariableTime("accessTime"); } } private void addLink(String method, String varName) { MethodSpec getter = MethodSpec.methodBuilder("get" + capitalize(method)) .addModifiers(Modifier.PUBLIC) .addStatement("return $N", varName) .returns(NODE) .build(); MethodSpec setter = MethodSpec.methodBuilder("set" + capitalize(method)) .addModifiers(Modifier.PUBLIC) .addParameter(NODE, varName) .addStatement("this.$N = $N", varName, varName) .build(); context.nodeSubtype .addMethod(getter) .addMethod(setter); } private void addVariableTime(String varName) { MethodSpec getter = MethodSpec.methodBuilder("getVariableTime") .addModifiers(Modifier.PUBLIC) .addStatement("return $N", varName) .returns(long.class) .build(); MethodSpec setter = MethodSpec.methodBuilder("setVariableTime") .addModifiers(Modifier.PUBLIC) .addParameter(long.class, varName) .addStatement("this.$N = $N", varName, varName) .build(); context.nodeSubtype .addMethod(getter) .addMethod(setter); } private void addAccessExpiration() { if (!context.generateFeatures.contains(Feature.EXPIRE_ACCESS)) { return; } context.nodeSubtype.addField(newFieldOffset(context.className, "accessTime")) .addField(long.class, "accessTime", Modifier.VOLATILE) .addMethod(newGetter(Strength.STRONG, TypeName.LONG, "accessTime", Visibility.LAZY)) .addMethod(newSetter(TypeName.LONG, "accessTime", Visibility.LAZY)); addTimeConstructorAssignment(context.constructorByKey, "accessTime"); addTimeConstructorAssignment(context.constructorByKeyRef, "accessTime"); } private void addWriteExpiration() { if (!Feature.useWriteTime(context.parentFeatures) && Feature.useWriteTime(context.generateFeatures)) { context.nodeSubtype.addField(newFieldOffset(context.className, "writeTime")) .addField(long.class, "writeTime", Modifier.VOLATILE) .addMethod(newGetter(Strength.STRONG, TypeName.LONG, "writeTime", Visibility.LAZY)) .addMethod(newSetter(TypeName.LONG, "writeTime", Visibility.LAZY)); addTimeConstructorAssignment(context.constructorByKey, "writeTime"); addTimeConstructorAssignment(context.constructorByKeyRef, "writeTime"); } } private void addRefreshExpiration() { if (!context.generateFeatures.contains(Feature.REFRESH_WRITE)) { return; } context.nodeSubtype.addMethod(MethodSpec.methodBuilder("casWriteTime") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addParameter(long.class, "expect") .addParameter(long.class, "update") .returns(boolean.class) .addStatement("return $T.UNSAFE.compareAndSwapLong(this, $N, $N, $N)", UNSAFE_ACCESS, offsetName("writeTime"), "expect", "update") .build()); } /** Adds a long constructor assignment. */ private void addTimeConstructorAssignment(MethodSpec.Builder constructor, String field) { constructor.addStatement("$T.UNSAFE.putLong(this, $N, $N)", UNSAFE_ACCESS, offsetName(field), "now"); } }