/* * Copyright 2014 Google Inc. 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 org.inferred.freebuilder.processor; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.inferred.freebuilder.processor.Metadata.Property; import org.inferred.freebuilder.processor.util.Block; import org.inferred.freebuilder.processor.util.Excerpt; import org.inferred.freebuilder.processor.util.SourceBuilder; import org.inferred.freebuilder.processor.util.StaticExcerpt; import java.lang.reflect.Field; import java.util.List; import java.util.Map; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; /** Property-type-specific code generation interface. */ public abstract class PropertyCodeGenerator { /** Data available to {@link Factory} instances when creating a {@link PropertyCodeGenerator}. */ interface Config { /** Returns metadata about the builder being generated. */ Metadata getMetadata(); /** Returns metadata about the property requiring code generation. */ Metadata.Property getProperty(); /** Returns annotations on the property requiring code generation. */ List<? extends AnnotationMirror> getAnnotations(); /** The user's Builder type. */ TypeElement getBuilder(); /** * A set of methods that are definitely invoked in the builder constructor. This may have false * negatives (e.g. if method introspection has not been implemented for the current compiler), * so must only be used for making optimizations. */ Set<String> getMethodsInvokedInBuilderConstructor(); /** The compiler's {@link Elements} implementation. */ Elements getElements(); /** The compiler's {@link Types} implementation. */ Types getTypes(); } /** Factory interface for {@link PropertyCodeGenerator}. */ interface Factory { /** * Create a new {@link PropertyCodeGenerator} for the property described in {@code config}. * * @return A new {@link PropertyCodeGenerator}, or {@link Optional#absent()} if the factory * does not support this type of property. */ Optional<? extends PropertyCodeGenerator> create(Config config); } protected final Metadata metadata; protected final Property property; public PropertyCodeGenerator(Metadata metadata, Property property) { this.metadata = metadata; this.property = property; } /** Property type. */ public enum Type { REQUIRED, OPTIONAL, HAS_DEFAULT } /** Returns whether the property is required, optional, or has a default. */ public Type getType() { return Type.HAS_DEFAULT; } /** Add the field declaration for the property to the value's source code. */ public void addValueFieldDeclaration(SourceBuilder code, String finalField) { code.addLine("private final %s %s;", property.getType(), finalField); } /** Add the field declaration for the property to the builder's source code. */ public abstract void addBuilderFieldDeclaration(SourceBuilder code); /** Add the accessor methods for the property to the builder's source code. */ public abstract void addBuilderFieldAccessors(SourceBuilder code); /** Add the final assignment of the property to the value object's source code. */ public abstract void addFinalFieldAssignment( SourceBuilder code, String finalField, String builder); /** Add the final assignment of the property to the partial value object's source code. */ public void addPartialFieldAssignment(SourceBuilder code, String finalField, String builder) { addFinalFieldAssignment(code, finalField, builder); } /** Add a merge from value for the property to the builder's source code. */ public abstract void addMergeFromValue(Block code, String value); /** Add a merge from builder for the property to the builder's source code. */ public abstract void addMergeFromBuilder(Block code, String builder); /** Sets the property on a builder from within a partial value's toBuilder() method. */ public void addSetBuilderFromPartial(Block code, String builder) { addSetFromResult(code, builder, property.getName()); } /** Adds method annotations for the value type getter method. */ public void addGetterAnnotations(@SuppressWarnings("unused") SourceBuilder code) {} /** Adds a fragment converting the value object's field to the property's type. */ public void addReadValueFragment(SourceBuilder code, String finalField) { code.add("%s", finalField); } /** Adds a set call for the property from a function result to the builder's source code. */ public abstract void addSetFromResult(SourceBuilder code, String builder, String variable); /** Adds a clear call for the property given a template builder to the builder's source code. */ public abstract void addClearField(Block code); /** Returns excerpts for any static types or methods added by this generator. */ public Set<? extends StaticExcerpt> getStaticExcerpts() { return ImmutableSet.of(); } protected void addAccessorAnnotations(SourceBuilder code) { for (Excerpt annotation : property.getAccessorAnnotations()) { code.add(annotation); } } @Override public boolean equals(final Object obj) { if (obj == null || !getClass().isInstance(obj)) { return false; } PropertyCodeGenerator other = (PropertyCodeGenerator) obj; return fieldValues().equals(other.fieldValues()); } @Override public int hashCode() { return ImmutableList.copyOf(fieldValues().values()).hashCode(); } @Override public String toString() { ToStringHelper stringHelper = MoreObjects.toStringHelper(this); for (Map.Entry<String, Object> fieldValue : fieldValues().entrySet()) { stringHelper.add(fieldValue.getKey(), fieldValue.getValue()); } return stringHelper.toString(); } private Map<String, Object> fieldValues() { ImmutableMap.Builder<String, Object> valuesBuilder = ImmutableMap.builder(); addFieldValues(getClass(), valuesBuilder); return valuesBuilder.build(); } private void addFieldValues(Class<?> cls, ImmutableMap.Builder<String, Object> valuesBuilder) { try { if (cls.getSuperclass() != null) { addFieldValues(cls.getSuperclass(), valuesBuilder); } for (Field field : cls.getDeclaredFields()) { field.setAccessible(true); valuesBuilder.put(field.getName(), field.get(this)); } } catch (IllegalAccessException e) { throw new AssertionError(e); } } }