/*
* Copyright 2016 Google Inc.
*
* 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.google.android.libraries.remixer.annotation.processor;
import com.google.android.libraries.remixer.DataType;
import com.google.android.libraries.remixer.ItemListVariable;
import com.google.android.libraries.remixer.annotation.ColorListVariableMethod;
import com.google.android.libraries.remixer.annotation.NumberListVariableMethod;
import com.google.android.libraries.remixer.annotation.StringListVariableMethod;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import java.util.ArrayList;
import java.util.Locale;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
/**
* Generates code to support {@link StringListVariableMethod} and {@link ColorListVariableMethod}
* annotations.
*/
class ItemListVariableMethodAnnotation<T> extends MethodAnnotation {
/**
* Suffix to add to the name of a list holding the set of values this variable can take.
*/
private static final String LIST_SUFFIX = "_variable_list";
/**
* Statement format for String.format to add a value to the list of limited to values.
*/
private static final String ADD_ITEM_FORMAT = "$L.add(%s)";
/**
* Statement format for String.format to set the initial value.
*/
private static final String SET_INITIAL_VALUE_FORMAT = "$L.setInitialValue(%s)";
/**
* Javapoet format escaping for float values.
*/
private static final String FLOAT_JAVAPOET_ESCAPING = "$Lf";
/**
* Javapoet format escaping for integer values.
*/
private static final String INTEGER_JAVAPOET_ESCAPING = "$L";
/**
* Javapoet format escaping for string values.
*/
private static final String STRING_JAVAPOET_ESCAPING = "$S";
/**
* Statement to create an object through a no-parameter constructor.
*
* <p>This is used to create the list of limited to values ({@code ArrayList<T> list =
* new ArrayList<T>()}.
*/
private static final String CREATE_NEW_OBJECT = "$T $L = new $T()";
private final T initialValue;
private T[] limitedToValues;
static ItemListVariableMethodAnnotation<String> forStringListVariableMethod(
TypeElement sourceClass,
ExecutableElement sourceMethod,
StringListVariableMethod annotation)
throws RemixerAnnotationException {
return new ItemListVariableMethodAnnotation<>(
sourceClass,
sourceMethod,
DataType.STRING,
ParameterizedTypeName.get(
ClassName.get(ItemListVariable.Builder.class), ClassName.get(String.class)),
annotation.key(),
annotation.title(),
annotation.layoutId(),
annotation.limitedToValues(),
annotation.initialValue(),
"");
}
static ItemListVariableMethodAnnotation<Integer> forColorListVariableMethod(
TypeElement sourceClass,
ExecutableElement sourceMethod,
ColorListVariableMethod annotation)
throws RemixerAnnotationException {
Integer[] limitedToValues = new Integer[annotation.limitedToValues().length];
for (int i = 0; i < annotation.limitedToValues().length; i++) {
limitedToValues[i] = annotation.limitedToValues()[i];
}
return new ItemListVariableMethodAnnotation<>(
sourceClass,
sourceMethod,
DataType.COLOR,
ParameterizedTypeName.get(
ClassName.get(ItemListVariable.Builder.class), ClassName.get(Integer.class)),
annotation.key(),
annotation.title(),
annotation.layoutId(),
limitedToValues,
annotation.initialValue(),
0);
}
static ItemListVariableMethodAnnotation<Float> forNumberListVariableMethod(
TypeElement sourceClass,
ExecutableElement sourceMethod,
NumberListVariableMethod annotation)
throws RemixerAnnotationException {
Float[] limitedToValues = new Float[annotation.limitedToValues().length];
for (int i = 0; i < annotation.limitedToValues().length; i++) {
limitedToValues[i] = annotation.limitedToValues()[i];
}
return new ItemListVariableMethodAnnotation<>(
sourceClass,
sourceMethod,
DataType.NUMBER,
ParameterizedTypeName.get(
ClassName.get(ItemListVariable.Builder.class), ClassName.get(Float.class)),
annotation.key(),
annotation.title(),
annotation.layoutId(),
limitedToValues,
annotation.initialValue(),
0f);
}
/**
* Constructs an ItemListVariableMethodAnnotation and makes sure that the constraints for these
* annotations are met.
*
* <p>If the default is unset and the zero value (0 for numbers, empty string for Strings) is not
* amongst the values this variable is limited to then it falls back to the first value in the
* list.
*
* @throws RemixerAnnotationException if the constraints are not met:
* - The list of limited to values is empty.
* - The default is explicitly set to an unknown value.
*/
private ItemListVariableMethodAnnotation(
TypeElement sourceClass,
ExecutableElement sourceMethod,
DataType dataType,
TypeName builderTypeName,
String key,
String title,
int layoutId,
T[] limitedToValues,
T initialValue,
T zeroValue)
throws RemixerAnnotationException {
super(sourceClass, sourceMethod, dataType, builderTypeName, key, title, layoutId);
this.limitedToValues = limitedToValues;
if (limitedToValues.length == 0) {
throw new RemixerAnnotationException(
sourceMethod, "List of limited to values cannot be empty");
}
boolean containsDefault = false;
for (T s : limitedToValues) {
if (s.equals(initialValue)) {
containsDefault = true;
break;
}
}
if (!containsDefault) {
if (initialValue.equals(zeroValue)) {
// If no initial value was set (empty) and the list of limited to values doesn't contain the
// zero-value (empty string in case of string, 0 in case of numbers) then assume the first
// value in the list to be the initial value.
this.initialValue = limitedToValues[0];
} else {
throw new RemixerAnnotationException(sourceMethod,
"Initial value explicitly set to unknown value");
}
} else {
this.initialValue = initialValue;
}
}
@Override
public void addSpecificSetupStatements(MethodSpec.Builder methodBuilder) {
String listName = key + LIST_SUFFIX;
TypeName listType =
ParameterizedTypeName.get(ClassName.get(ArrayList.class),
ClassName.get(dataType.getRuntimeType()));
methodBuilder.addStatement(CREATE_NEW_OBJECT, listType, listName, listType);
String addValueStatement =
String.format(Locale.getDefault(), ADD_ITEM_FORMAT, getJavaPoetEscaping());
for (T value : limitedToValues) {
methodBuilder.addStatement(addValueStatement, listName, value);
}
methodBuilder.addStatement("$L.setLimitedToValues($L)", remixerItemName, listName);
String setInitialValueStatement =
String.format(Locale.getDefault(), SET_INITIAL_VALUE_FORMAT, getJavaPoetEscaping());
methodBuilder.addStatement(setInitialValueStatement, remixerItemName, initialValue);
}
private String getJavaPoetEscaping() {
if (dataType.getName().equals(DataType.STRING.getName())) {
return STRING_JAVAPOET_ESCAPING;
} else if (dataType.getName().equals(DataType.COLOR.getName())) {
return INTEGER_JAVAPOET_ESCAPING;
}
// If it is an item list it can only be either String, Color or a number, since it's neither
// color nor number at this point, just assume that it's a number so return
// FLOAT_JAVAPOET_ESCAPING.
return FLOAT_JAVAPOET_ESCAPING;
}
}