Java tutorial
/* * 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; import static com.github.benmanes.caffeine.cache.Specifications.DEAD_STRONG_KEY; import static com.github.benmanes.caffeine.cache.Specifications.DEAD_WEAK_KEY; import static com.github.benmanes.caffeine.cache.Specifications.NODE; import static com.github.benmanes.caffeine.cache.Specifications.PACKAGE_NAME; import static com.github.benmanes.caffeine.cache.Specifications.RETIRED_STRONG_KEY; import static com.github.benmanes.caffeine.cache.Specifications.RETIRED_WEAK_KEY; import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS; import static com.github.benmanes.caffeine.cache.Specifications.kTypeVar; import static com.github.benmanes.caffeine.cache.Specifications.keyRefQueueSpec; import static com.github.benmanes.caffeine.cache.Specifications.keyRefSpec; import static com.github.benmanes.caffeine.cache.Specifications.keySpec; import static com.github.benmanes.caffeine.cache.Specifications.newFieldOffset; import static com.github.benmanes.caffeine.cache.Specifications.nodeType; import static com.github.benmanes.caffeine.cache.Specifications.offsetName; import static com.github.benmanes.caffeine.cache.Specifications.vRefQueueType; import static com.github.benmanes.caffeine.cache.Specifications.vTypeVar; import static com.github.benmanes.caffeine.cache.Specifications.valueRefQueueSpec; import static com.github.benmanes.caffeine.cache.Specifications.valueSpec; import static com.google.common.base.Preconditions.checkState; import java.lang.ref.Reference; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import javax.lang.model.element.Modifier; import com.google.common.collect.Iterables; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; /** * Generates a node implementation. * * @author ben.manes@gmail.com (Ben Manes) */ public final class NodeGenerator { private final boolean isFinal; private final String className; private final TypeName superClass; private final Set<Feature> parentFeatures; private final Set<Feature> generateFeatures; private TypeSpec.Builder nodeSubtype; private MethodSpec.Builder constructorByKey; private MethodSpec.Builder constructorByKeyRef; public NodeGenerator(TypeName superClass, String className, boolean isFinal, Set<Feature> parentFeatures, Set<Feature> generateFeatures) { this.isFinal = isFinal; this.className = className; this.superClass = superClass; this.parentFeatures = parentFeatures; this.generateFeatures = generateFeatures; } /** Returns an node class implementation optimized for the provided configuration. */ public TypeSpec.Builder createNodeType() { makeNodeSubtype(); makeBaseConstructorByKey(); makeBaseConstructorByKeyRef(); addKey(); addValue(); addWeight(); addExpiration(); addDeques(); addStateMethods(); addToString(); return nodeSubtype.addMethod(constructorByKey.build()).addMethod(constructorByKeyRef.build()); } private boolean isBaseClass() { return superClass.equals(TypeName.OBJECT); } private boolean isStrongKeys() { return parentFeatures.contains(Feature.STRONG_KEYS) || generateFeatures.contains(Feature.STRONG_KEYS); } private boolean isStrongValues() { return parentFeatures.contains(Feature.STRONG_VALUES) || generateFeatures.contains(Feature.STRONG_VALUES); } private void makeNodeSubtype() { nodeSubtype = TypeSpec.classBuilder(className).addModifiers(Modifier.STATIC).addTypeVariable(kTypeVar) .addTypeVariable(vTypeVar); if (isFinal) { nodeSubtype.addModifiers(Modifier.FINAL); } if (isBaseClass()) { nodeSubtype.addSuperinterface(ParameterizedTypeName.get(nodeType, kTypeVar, vTypeVar)); } else { nodeSubtype.superclass(superClass); } } private void addKey() { if (!isBaseClass()) { return; } Strength keyStrength = strengthOf(Iterables.get(generateFeatures, 0)); nodeSubtype.addField(newFieldOffset(className, "key")).addField(newKeyField()) .addMethod(newGetter(keyStrength, kTypeVar, "key", Visibility.LAZY)).addMethod(newGetKeyRef()); addKeyConstructorAssignment(constructorByKey, false); addKeyConstructorAssignment(constructorByKeyRef, true); } private void addValue() { if (!isBaseClass()) { return; } Strength valueStrength = strengthOf(Iterables.get(generateFeatures, 1)); nodeSubtype.addField(newFieldOffset(className, "value")).addField(newValueField()) .addMethod(newGetter(valueStrength, vTypeVar, "value", Visibility.LAZY)).addMethod(makeSetValue()) .addMethod(makeContainsValue()); addValueConstructorAssignment(constructorByKey); addValueConstructorAssignment(constructorByKeyRef); } /** Creates the setValue method. */ private MethodSpec makeSetValue() { MethodSpec.Builder setter = MethodSpec.methodBuilder("setValue") .addModifiers(Modifier.PUBLIC, Modifier.FINAL).addParameter(vTypeVar, "value") .addParameter(vRefQueueType, "referenceQueue"); if (isStrongValues()) { setter.addStatement("$T.UNSAFE.putObject(this, $N, $N)", UNSAFE_ACCESS, offsetName("value"), "value"); } else { setter.addStatement("$T.UNSAFE.putObject(this, $N, new $T($L, $N, referenceQueue))", UNSAFE_ACCESS, offsetName("value"), valueReferenceType(), "key", "value"); } return setter.build(); } private MethodSpec makeContainsValue() { MethodSpec.Builder containsValue = MethodSpec.methodBuilder("containsValue") .addModifiers(Modifier.PUBLIC, Modifier.FINAL).addParameter(Object.class, "value") .returns(boolean.class); if (isStrongValues()) { containsValue.addStatement("return $T.equals(value, getValue())", Objects.class); } else { containsValue.addStatement("return getValue() == value"); } return containsValue.build(); } private FieldSpec newKeyField() { Modifier[] modifiers = { Modifier.PROTECTED, Modifier.VOLATILE }; FieldSpec.Builder fieldSpec = isStrongKeys() ? FieldSpec.builder(kTypeVar, "key", modifiers) : FieldSpec.builder(keyReferenceType(), "key", modifiers); return fieldSpec.build(); } private FieldSpec newValueField() { Modifier[] modifiers = { Modifier.PROTECTED, Modifier.VOLATILE }; FieldSpec.Builder fieldSpec = isStrongValues() ? FieldSpec.builder(vTypeVar, "value", modifiers) : FieldSpec.builder(valueReferenceType(), "value", modifiers); return fieldSpec.build(); } /** Adds the constructor by key to the node type. */ private void makeBaseConstructorByKey() { constructorByKey = MethodSpec.constructorBuilder().addParameter(keySpec); constructorByKey.addParameter(keyRefQueueSpec); completeBaseConstructor(constructorByKey); if (!isBaseClass()) { constructorByKey.addStatement("super(key, keyReferenceQueue, value, valueReferenceQueue, weight, now)"); } } /** Adds the constructor by key reference to the node type. */ private void makeBaseConstructorByKeyRef() { constructorByKeyRef = MethodSpec.constructorBuilder().addParameter(keyRefSpec); completeBaseConstructor(constructorByKeyRef); if (!isBaseClass()) { constructorByKeyRef.addStatement("super(keyReference, value, valueReferenceQueue, weight, now)"); } } private void completeBaseConstructor(MethodSpec.Builder constructor) { constructor.addParameter(valueSpec); constructor.addParameter(valueRefQueueSpec); constructor.addParameter(int.class, "weight"); constructor.addParameter(long.class, "now"); } /** Adds a constructor assignment. */ private void addKeyConstructorAssignment(MethodSpec.Builder constructor, boolean isReference) { if (isReference || isStrongKeys()) { String refAssignment = isStrongKeys() ? "(K) keyReference" : "(WeakKeyReference<K>) keyReference"; constructor.addStatement("$T.UNSAFE.putObject(this, $N, $N)", UNSAFE_ACCESS, offsetName("key"), isReference ? refAssignment : "key"); } else { constructor.addStatement("$T.UNSAFE.putObject(this, $N, new $T($N, $N))", UNSAFE_ACCESS, offsetName("key"), keyReferenceType(), "key", "keyReferenceQueue"); } } /** Adds a constructor assignment. */ private void addValueConstructorAssignment(MethodSpec.Builder constructor) { if (isStrongValues()) { constructor.addStatement("$T.UNSAFE.putObject(this, $N, $N)", UNSAFE_ACCESS, offsetName("value"), "value"); } else { constructor.addStatement("$T.UNSAFE.putObject(this, $N, new $T(this.$N, $N, $N))", UNSAFE_ACCESS, offsetName("value"), valueReferenceType(), "key", "value", "valueReferenceQueue"); } } /** Adds weight support, if enabled, to the node type. */ private void addWeight() { if (generateFeatures.contains(Feature.MAXIMUM_WEIGHT)) { nodeSubtype.addField(int.class, "weight", Modifier.PROTECTED) .addMethod(newGetter(Strength.STRONG, TypeName.INT, "weight", Visibility.IMMEDIATE)) .addMethod(newSetter(TypeName.INT, "weight", Visibility.IMMEDIATE)); addIntConstructorAssignment(constructorByKey, "weight", "weight", Visibility.IMMEDIATE); addIntConstructorAssignment(constructorByKeyRef, "weight", "weight", Visibility.IMMEDIATE); } } /** Adds the expiration support, if enabled, to the node type. */ private void addExpiration() { if (generateFeatures.contains(Feature.EXPIRE_ACCESS)) { nodeSubtype.addField(newFieldOffset(className, "accessTime")) .addField(long.class, "accessTime", Modifier.PROTECTED, Modifier.VOLATILE) .addMethod(newGetter(Strength.STRONG, TypeName.LONG, "accessTime", Visibility.LAZY)) .addMethod(newSetter(TypeName.LONG, "accessTime", Visibility.LAZY)); addLongConstructorAssignment(constructorByKey, "now", "accessTime", Visibility.LAZY); addLongConstructorAssignment(constructorByKeyRef, "now", "accessTime", Visibility.LAZY); } if (!Feature.useWriteTime(parentFeatures) && Feature.useWriteTime(generateFeatures)) { nodeSubtype.addField(newFieldOffset(className, "writeTime")) .addField(long.class, "writeTime", Modifier.PROTECTED, Modifier.VOLATILE) .addMethod(newGetter(Strength.STRONG, TypeName.LONG, "writeTime", Visibility.LAZY)) .addMethod(newSetter(TypeName.LONG, "writeTime", Visibility.LAZY)); addLongConstructorAssignment(constructorByKey, "now", "writeTime", Visibility.LAZY); addLongConstructorAssignment(constructorByKeyRef, "now", "writeTime", Visibility.LAZY); } if (generateFeatures.contains(Feature.REFRESH_WRITE)) { 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 integer constructor assignment. */ private void addIntConstructorAssignment(MethodSpec.Builder constructor, String param, String field, Visibility visibility) { if (visibility.isRelaxed) { constructor.addStatement("$T.UNSAFE.putInt(this, $N, $N)", UNSAFE_ACCESS, offsetName(field), param); constructor.addStatement("this.$N = $N", field, param); } else { constructor.addStatement("this.$N = $N", field, param); } } /** Adds a long constructor assignment. */ private void addLongConstructorAssignment(MethodSpec.Builder constructor, String param, String field, Visibility visibility) { if (visibility.isRelaxed) { constructor.addStatement("$T.UNSAFE.putLong(this, $N, $N)", UNSAFE_ACCESS, offsetName(field), param); } else { constructor.addStatement("this.$N = $N", field, param); } } /** Adds the access and write deques, if needed, to the type. */ private void addDeques() { if (!Feature.usesAccessOrderDeque(parentFeatures) && Feature.usesAccessOrderDeque(generateFeatures)) { addFieldAndGetter(nodeSubtype, NODE, "previousInAccessOrder"); addFieldAndGetter(nodeSubtype, NODE, "nextInAccessOrder"); } if (!Feature.usesWriteOrderDeque(parentFeatures) && Feature.usesWriteOrderDeque(generateFeatures)) { addFieldAndGetter(nodeSubtype, NODE, "previousInWriteOrder"); addFieldAndGetter(nodeSubtype, NODE, "nextInWriteOrder"); } } /** Adds a simple field, accessor, and mutator for the variable. */ private void addFieldAndGetter(TypeSpec.Builder typeSpec, TypeName varType, String varName) { typeSpec.addField(varType, varName, Modifier.PROTECTED) .addMethod(newGetter(Strength.STRONG, varType, varName, Visibility.IMMEDIATE)) .addMethod(newSetter(varType, varName, Visibility.IMMEDIATE)); } private void addStateMethods() { if (!isBaseClass()) { return; } String retiredArg; String deadArg; if (generateFeatures.contains(Feature.STRONG_KEYS)) { retiredArg = RETIRED_STRONG_KEY; deadArg = DEAD_STRONG_KEY; } else { retiredArg = RETIRED_WEAK_KEY; deadArg = DEAD_WEAK_KEY; } String keyOffset = isBaseClass() ? offsetName("key") : baseClassName() + '.' + offsetName("key"); nodeSubtype.addMethod(MethodSpec.methodBuilder("isAlive").addStatement("Object key = getKeyReference()") .addStatement("return (key != $L) && (key != $L)", retiredArg, deadArg) .addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(boolean.class).build()); nodeSubtype.addMethod( MethodSpec.methodBuilder("isRetired").addStatement("return (getKeyReference() == $L)", retiredArg) .addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(boolean.class).build()); nodeSubtype.addMethod(MethodSpec.methodBuilder("retire") .addStatement("$T.UNSAFE.putObject(this, $N, $N)", UNSAFE_ACCESS, keyOffset, retiredArg) .addModifiers(Modifier.PUBLIC, Modifier.FINAL).build()); nodeSubtype.addMethod( MethodSpec.methodBuilder("isDead").addStatement("return (getKeyReference() == $L)", deadArg) .addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(boolean.class).build()); nodeSubtype.addMethod(MethodSpec.methodBuilder("die") .addStatement("$T.UNSAFE.putObject(this, $N, $N)", UNSAFE_ACCESS, keyOffset, deadArg) .addModifiers(Modifier.PUBLIC, Modifier.FINAL).build()); } private String baseClassName() { List<Feature> keyAndValue = parentFeatures.stream() .filter(feature -> feature.name().endsWith("KEYS") || feature.name().endsWith("VALUES")) .collect(Collectors.toList()); return Feature.makeClassName(keyAndValue); } /** Creates an accessor that returns the key reference. */ private MethodSpec newGetKeyRef() { MethodSpec.Builder getter = MethodSpec.methodBuilder("getKeyReference") .addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(Object.class); getter.addStatement("return $T.UNSAFE.getObject(this, $N)", UNSAFE_ACCESS, offsetName("key")); return getter.build(); } /** Creates an accessor that returns the unwrapped variable. */ private MethodSpec newGetter(Strength strength, TypeName varType, String varName, Visibility visibility) { String methodName = "get" + Character.toUpperCase(varName.charAt(0)) + varName.substring(1); MethodSpec.Builder getter = MethodSpec.methodBuilder(methodName) .addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(varType); String type; if (varType.isPrimitive()) { type = (varType == TypeName.INT) ? "Int" : "Long"; } else { type = "Object"; } if (strength == Strength.STRONG) { if (visibility.isRelaxed) { if (varType.isPrimitive()) { getter.addStatement("return $T.UNSAFE.get$N(this, $N)", UNSAFE_ACCESS, type, offsetName(varName)); } else { getter.addStatement("return ($T) $T.UNSAFE.get$N(this, $N)", varType, UNSAFE_ACCESS, type, offsetName(varName)); } } else { getter.addStatement("return $N", varName); } } else { if (visibility.isRelaxed) { getter.addStatement("return (($T<$T>) $T.UNSAFE.get$N(this, $N)).get()", Reference.class, varType, UNSAFE_ACCESS, type, offsetName(varName)); } else { getter.addStatement("return $N.get()", varName); } } return getter.build(); } /** Creates a mutator to the variable. */ private MethodSpec newSetter(TypeName varType, String varName, Visibility visibility) { String methodName = "set" + Character.toUpperCase(varName.charAt(0)) + varName.substring(1); String type; if (varType.isPrimitive()) { type = (varType == TypeName.INT) ? "Int" : "Long"; } else { type = "Object"; } MethodSpec.Builder setter = MethodSpec.methodBuilder(methodName) .addModifiers(Modifier.PUBLIC, Modifier.FINAL).addParameter(varType, varName); if (visibility.isRelaxed) { setter.addStatement("$T.UNSAFE.put$L(this, $N, $N)", UNSAFE_ACCESS, type, offsetName(varName), varName); } else { setter.addStatement("this.$N = $N", varName, varName); } return setter.build(); } /** Generates a toString method with the custom fields. */ private void addToString() { if (!isBaseClass()) { return; } String statement = "return String.format(\"%s=[key=%s, value=%s, weight=%d, accessTimeNS=%,d, " + "writeTimeNS=%,d, \"\n+ \"prevInAccess=%s, nextInAccess=%s, prevInWrite=%s, " + "nextInWrite=%s]\",\ngetClass().getSimpleName(), getKey(), getValue(), getWeight(), " + "getAccessTime(),\ngetWriteTime(), getPreviousInAccessOrder() != null, " + "getNextInAccessOrder() != null,\ngetPreviousInWriteOrder() != null, " + "getNextInWriteOrder() != null)"; nodeSubtype.addMethod(MethodSpec.methodBuilder("toString").addModifiers(Modifier.PUBLIC, Modifier.FINAL) .returns(String.class).addStatement(statement).build()); } private TypeName keyReferenceType() { checkState(generateFeatures.contains(Feature.WEAK_KEYS)); return ParameterizedTypeName.get(ClassName.get(PACKAGE_NAME + ".References", "WeakKeyReference"), kTypeVar); } private TypeName valueReferenceType() { checkState(!generateFeatures.contains(Feature.STRONG_VALUES)); String clazz = generateFeatures.contains(Feature.WEAK_VALUES) ? "WeakValueReference" : "SoftValueReference"; return ParameterizedTypeName.get(ClassName.get(PACKAGE_NAME + ".References", clazz), vTypeVar); } Strength strengthOf(Feature feature) { for (Strength strength : Strength.values()) { if (feature.name().startsWith(strength.name())) { return strength; } } throw new IllegalStateException("No strength for " + feature); } enum Strength { STRONG, WEAK, SOFT; } enum Visibility { IMMEDIATE(false), LAZY(true); final boolean isRelaxed; private Visibility(boolean mode) { this.isRelaxed = mode; } } }