/*
Copyright 2014-2015 Immutables Authors and Contributors
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.immutables.value.processor.meta;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import org.immutables.generator.Naming;
import org.immutables.generator.Naming.Preference;
import org.immutables.generator.SourceExtraction;
import org.immutables.generator.SourceTypes;
import org.immutables.value.Value;
import org.immutables.value.processor.meta.Proto.DeclaringType;
import org.immutables.value.processor.meta.Proto.Protoclass;
import org.immutables.value.processor.meta.Styles.PackageNaming;
import org.immutables.value.processor.meta.Styles.UsingName.TypeNames;
import org.immutables.value.processor.meta.ValueMirrors.Style.ImplementationVisibility;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
@Value.Enclosing
@Value.Immutable
public abstract class Constitution {
private static final String NA_ERROR = "!should_not_be_used_in_generated_code!";
private static final String NEW_KEYWORD = "new";
private static final Joiner DOT_JOINER = Joiner.on('.').skipNulls();
public abstract Protoclass protoclass();
@Value.Lazy
public Generics generics() {
return new Generics(protoclass(),
protoclass().kind().isConstructor()
? protoclass().sourceElement().getEnclosingElement()
: protoclass().sourceElement());
}
@Value.Derived
public Visibility implementationVisibility() {
if (style().visibility() == ImplementationVisibility.PRIVATE
&& !protoclass().features().builder()
&& !protoclass().kind().isNested()) {
protoclass()
.report()
.warning("effective Style.visibility cannot be PRIVATE when builder is disabled and is not nested,"
+ " automatically switching visibility to PACKAGE because top level implementation class is required");
return Visibility.PACKAGE;
}
return protoclass().visibility().forImplementation(style().visibility());
}
@Value.Derived
public Visibility builderVisibility() {
return protoclass().visibility().forBuilder(style().builderVisibility());
}
public boolean isImplementationHidden() {
return implementationVisibility().isPrivate();
}
public boolean returnsAbstractValueType() {
return isImplementationHidden()
|| style().visibility() == ValueMirrors.Style.ImplementationVisibility.SAME_NON_RETURNED
|| style().overshadowImplementation()
|| (style().implementationNestedInBuilder()
&& implementationVisibility().isMoreRestrictiveThan(builderVisibility()));
}
public boolean isImplementationPrimary() {
return protoclass().visibility().isMoreRestrictiveThan(implementationVisibility());
}
@Value.Derived
public String implementationPackage() {
PackageNaming naming = protoclass().styles().packageGenerated();
return naming.apply(definingPackage());
}
public String definingPackage() {
return protoclass().packageOf().name();
}
@Value.Derived
public TypeNames names() {
return protoclass().createTypeNames();
}
@Value.Lazy
public NameForms typePreferablyAbstract() {
if (protoclass().kind().isValue()) {
return isImplementationPrimary()
? typeImmutable()
: typeAbstract();
}
return typeValue();
}
@Value.Lazy
public NameForms typeDocument() {
if (protoclass().kind().isValue()) {
return isAbstractPrimary()
? typeAbstract()
: typeImmutable();
}
return typeValue();
}
@Value.Lazy
public NameForms typeModifiable() {
checkState(protoclass().kind().isModifiable());
String simple = names().typeModifiable();
return ImmutableConstitution.NameForms.builder()
.simple(simple)
.relativeRaw(inPackage(simple))
.genericArgs(generics().args())
.packageOf(implementationPackage())
.visibility(implementationVisibility())
.build();
}
@Value.Lazy
public AppliedNameForms factoryCreate() {
return typeModifiable().applied(names().create());
}
private boolean isAbstractPrimary() {
return returnsAbstractValueType()
|| !isImplementationPrimary();
}
public boolean isSimple() {
return protoclass().kind().isValue()
&& !protoclass().kind().isNested()
&& implementationVisibility().isPublic()
&& !returnsAbstractValueType();
}
/**
* Value is the canonical outside look of the value type. It should be either
* {@link #typeAbstract()} or {@link #typeImmutable()}.
* For factory it is a special surrogate.
* @return canonical value type name forms
*/
@Value.Lazy
public NameForms typeValue() {
if (protoclass().kind().isValue()) {
return returnsAbstractValueType()
? typeAbstract()
: typeImmutable();
}
if (isFactory()) {
if (protoclass().kind().isConstructor()) {
TypeElement enclosingType = (TypeElement) protoclass().sourceElement().getEnclosingElement();
return ImmutableConstitution.NameForms.builder()
.simple(enclosingType.getSimpleName().toString())
.relativeRaw(enclosingType.getQualifiedName().toString())
.genericArgs(generics().args())
.relativeAlreadyQualified(true)
.packageOf(NA_ERROR)
.visibility(protoclass().visibility())
.build();
}
ExecutableElement method = (ExecutableElement) protoclass().sourceElement();
String type = method.getReturnType().toString();
return ImmutableConstitution.NameForms.builder()
.simple(NA_ERROR)
.relativeRaw(type)
.packageOf(NA_ERROR)
.relativeAlreadyQualified(true)
.visibility(protoclass().visibility())
.build();
}
return typeEnclosing();
}
@Value.Derived
public boolean hasImmutableInBuilder() {
return isOutsideBuilder() && isTopLevelValue();
}
public boolean hasTopLevelBuilder() {
return isFactory() || (isTopLevelValue() && isOutsideBuilder());
}
private boolean isFactory() {
return protoclass().kind().isFactory();
}
public boolean hasTopLevelImmutable() {
return isTopLevelValue()
&& !hasImmutableInBuilder();
}
public boolean isOutsideBuilder() {
return isFactory()
|| (protoclass().features().builder()
&& (isImplementationHidden()
|| style().implementationNestedInBuilder()));
}
private boolean isTopLevelValue() {
return protoclass().kind().isValue()
&& !protoclass().kind().isNested();
}
public boolean hasEnclosingNonvalue() {
return protoclass().kind().isEnclosing()
&& !protoclass().kind().isValue();
}
/**
* Actual abstract value type that is definitive model for the value type.
* @return abstract value type name forms
*/
@Value.Lazy
public NameForms typeAbstract() {
if (protoclass().kind().isConstructor()) {
return typeValue();
}
List<String> classSegments = Lists.newArrayListWithExpectedSize(2);
Element e = SourceNames.collectClassSegments(protoclass().sourceElement(), classSegments);
verify(e instanceof PackageElement);
String packageOf = ((PackageElement) e).getQualifiedName().toString();
String relative = DOT_JOINER.join(classSegments);
boolean relativeAlreadyQualified = false;
if (!implementationPackage().equals(packageOf)) {
relative = DOT_JOINER.join(packageOf, relative);
relativeAlreadyQualified = true;
}
return ImmutableConstitution.NameForms.builder()
.simple(names().typeAbstract)
.relativeRaw(relative)
.packageOf(packageOf)
.genericArgs(generics().args())
.relativeAlreadyQualified(relativeAlreadyQualified)
.visibility(protoclass().visibility())
.build();
}
public StyleInfo style() {
return protoclass().styles().style();
}
/**
* Package relative path
* @param topLevel
* @param nested
* @return
*/
private String inPackage(String topLevel, String... nested) {
return DOT_JOINER.join(null, topLevel, (Object[]) nested);
}
/**
* Actual immutable value type generated implementation.
* @return immutable implementation type name forms
*/
@Value.Lazy
public NameForms typeImmutable() {
String simple, relative;
if (protoclass().kind().isNested()) {
String enclosingSimpleName = typeImmutableEnclosingSimpleName();
simple = names().typeImmutableNested();
relative = inPackage(enclosingSimpleName, simple);
} else if (hasImmutableInBuilder()) {
simple = names().typeImmutable;
relative = inPackage(typeBuilderSimpleName(), simple);
} else {
simple = names().typeImmutable;
relative = inPackage(simple);
}
return ImmutableConstitution.NameForms.builder()
.simple(simple)
.relativeRaw(relative)
.genericArgs(generics().args())
.packageOf(implementationPackage())
.visibility(implementationVisibility())
.build();
}
/**
* Walks to the enclosing type's simple names and applies naming convention.
* This shortcut/fix shows deficiency of model (it's probably more complicated than needed).
* @return enclosing immutable name
*/
@Value.Lazy
String typeImmutableEnclosingSimpleName() {
DeclaringType declaringType = protoclass().enclosingOf().get();
String enclosingSimpleName = declaringType.element().getSimpleName().toString();
String enclosingRawName = names().rawFromAbstract(enclosingSimpleName);
// Here we checking for having both enclosing and value
// if we had protoclass it would be kind().isEnclosing() && kind().isValue()
Naming naming = declaringType.isImmutable()
? names().namings.typeImmutable
: names().namings.typeImmutableEnclosing;
return naming.apply(enclosingRawName);
}
private String typeBuilderSimpleName() {
boolean isOutside = isOutsideBuilder();
Naming typeBuilderNaming = names().namings.typeBuilder;
if (isOutside) {
// For outer builder we can override with constant builder naming, but not the default.
boolean isPlainDefault =
isConstantNamingEquals(typeBuilderNaming, protoclass().environment().defaultStyles().typeBuilder());
if (isPlainDefault) {
typeBuilderNaming = typeBuilderNaming.requireNonConstant(Preference.SUFFIX);
}
}
return Naming.Usage.CAPITALIZED.apply(typeBuilderNaming.apply(names().raw));
}
@Value.Lazy
public AppliedNameForms factoryBuilder() {
InnerBuilderDefinition innerBuilder = innerBuilder();
if (innerBuilder.isExtending) {
return typeBuilder().applied(NEW_KEYWORD);
}
return factoryImplementationBuilder();
}
private AppliedNameForms factoryImplementationBuilder() {
boolean isOutside = isOutsideBuilder();
Naming methodBuilderNaming = isOutside
? names().namings.newBuilder
: names().namings.builder;
boolean haveConstructorOnBuilder = isOutside
|| isConstantNamingEquals(methodBuilderNaming, NEW_KEYWORD);
NameForms typeNameForms = haveConstructorOnBuilder
? typeBuilder()
: typeImmutable();
return typeNameForms.applied(methodBuilderNaming.apply(names().raw));
}
private boolean isConstantNamingEquals(Naming naming, String name) {
return naming.isConstant()
&& naming.apply("").equals(name);
}
@Value.Lazy
public AppliedNameForms factoryOf() {
if (isFactory()) {
TypeElement enclosingType = (TypeElement) protoclass().sourceElement().getEnclosingElement();
String invoke = protoclass().kind().isConstructor()
? "new"
: protoclass().sourceElement().getSimpleName().toString();
return ImmutableConstitution.NameForms.builder()
.simple(enclosingType.getSimpleName().toString())
.relativeRaw(enclosingType.getQualifiedName().toString())
.genericArgs(generics().args())
.relativeAlreadyQualified(true)
.packageOf(NA_ERROR)
.visibility(protoclass().visibility())
.build()
.applied(invoke);
}
return applyFactoryNaming(names().namings.of);
}
@Value.Lazy
public AppliedNameForms factoryInstance() {
return applyFactoryNaming(names().namings.instance);
}
@Value.Lazy
public AppliedNameForms factoryCopyOf() {
return applyFactoryNaming(names().namings.copyOf);
}
private AppliedNameForms applyFactoryNaming(Naming naming) {
String raw = names().raw;
boolean hasForwardingFactoryMethods = isImplementationHidden()
&& protoclass().kind().isNested();
NameForms nameForms = hasForwardingFactoryMethods
? typeEnclosingFactory()
: typeImmutable();
if (hasForwardingFactoryMethods) {
naming = naming.requireNonConstant(Preference.PREFIX);
}
String applyName = Naming.Usage.LOWERIZED.apply(naming.apply(raw));
return nameForms.applied(applyName);
}
@Value.Lazy
public NameForms typeEnclosingFactory() {
String enclosingSimpleName = typeImmutableEnclosingSimpleName();
return ImmutableConstitution.NameForms.builder()
.simple(enclosingSimpleName)
.relativeRaw(enclosingSimpleName)
.packageOf(implementationPackage())
.visibility(protoclass().declaringVisibility())
.build();
}
@Value.Lazy
public NameForms typeEnclosing() {
String name = protoclass().kind().isDefinedValue()
? names().typeImmutable
: names().typeImmutableEnclosing();
return ImmutableConstitution.NameForms.builder()
.simple(name)
.relativeRaw(name)
.packageOf(implementationPackage())
.visibility(implementationEnclosingVisibility())
.build();
}
private Visibility implementationEnclosingVisibility() {
return implementationVisibility().max(Visibility.PACKAGE);
}
@Value.Lazy
public NameForms typeWith() {
String simple, relative;
if (protoclass().kind().isNested()) {
String enclosingSimpleName = typeImmutableEnclosingSimpleName();
simple = names().typeWith();
relative = inPackage(enclosingSimpleName, simple);
} else if (hasImmutableInBuilder()) {
simple = names().typeWith();
relative = inPackage(typeBuilderSimpleName(), simple);
} else {
simple = names().typeWith();
relative = inPackage(simple);
}
return ImmutableConstitution.NameForms.builder()
.simple(simple)
.relativeRaw(relative)
.genericArgs(generics().args())
.packageOf(implementationPackage())
.visibility(implementationVisibility())
.build();
}
@Value.Lazy
public NameForms typeBuilder() {
InnerBuilderDefinition innerBuilder = innerBuilder();
if (innerBuilder.isExtending) {
NameForms typeAbstract = typeAbstract();
return ImmutableConstitution.NameForms.copyOf(typeAbstract)
.withRelativeRaw(DOT_JOINER.join(typeAbstract.relativeRaw(), innerBuilder.simpleName))
.withSimple(innerBuilder.simpleName);
}
return typeImplementationBuilder();
}
@Value.Lazy
public NameForms typeImplementationBuilder() {
TypeNames names = names();
boolean outside = isOutsideBuilder();
boolean nested = protoclass().kind().isNested();
String simple = typeBuilderSimpleName();
String relative;
if (outside && nested) {
relative = inPackage(typeImmutableEnclosingSimpleName(), simple);
} else if (outside) {
relative = inPackage(simple);
} else if (nested) {
relative = inPackage(inPackage(typeImmutableEnclosingSimpleName(), names.typeImmutableNested(), simple));
} else {
relative = inPackage(inPackage(names.typeImmutable, simple));
}
Visibility visibility = builderVisibility();
if (!outside) {
visibility = visibility.min(implementationVisibility());
}
return ImmutableConstitution.NameForms.builder()
.simple(simple)
.relativeRaw(relative)
.genericArgs(generics().args())
.packageOf(implementationPackage())
.visibility(visibility)
.build();
}
@Value.Immutable
public static abstract class AppliedNameForms extends AbstractNameForms {
public abstract NameForms forms();
public abstract String applied();
@Override
@Value.Derived
public String simple() {
return isNew()
? (NEW_KEYWORD + ' ' + forms().simple())
: applied();
}
@Override
public String relativeRaw() {
return isNew()
? (NEW_KEYWORD + ' ' + forms().relativeRaw())
: (forms().relativeRaw() + '.' + applied());
}
@Override
public String relative() {
return combineApplied(false);
}
@Value.Derived
public boolean isNew() {
return NEW_KEYWORD.equals(applied());
}
@Override
public String toString() {
if (relativeAlreadyQualified()) {
return relative();
}
return combineApplied(true);
}
@Override
public String genericArgs() {
return forms().genericArgs();
}
@Override
public String packageOf() {
return forms().packageOf();
}
@Override
public Visibility visibility() {
return forms().visibility();
}
@Override
public boolean relativeAlreadyQualified() {
return forms().relativeAlreadyQualified();
}
private String combineApplied(boolean qualifyWithPackage) {
String base = forms().relativeRaw();
if (qualifyWithPackage) {
base = qualifyWithPackage(base);
}
return isNew()
? (NEW_KEYWORD + ' ' + base + genericArgs())
: (base + '.' + genericArgs() + applied());
}
}
public static abstract class AbstractNameForms {
private static final String PUBLIC_MODIFIER_PREFIX = "public ";
private static final String PRIVATE_MODIFIER_PREFIX = "private ";
public abstract String simple();
public abstract String relativeRaw();
public abstract String packageOf();
public abstract Visibility visibility();
@Value.Default
public String absolute(){
return DOT_JOINER.join(packageOf(), relative());
}
@Value.Default
public String absoluteRaw(){
return DOT_JOINER.join(packageOf(), relativeRaw());
}
@Value.Default
public String genericArgs() {
return "";
}
@Value.Default
public boolean relativeAlreadyQualified() {
return false;
}
public String relative() {
return relativeRaw() + genericArgs();
}
/**
* Access prefix. Includes trailing space separator if not empty (package private).
* @return access keyword text
*/
public String access() {
switch (visibility()) {
case PRIVATE:
return PRIVATE_MODIFIER_PREFIX;
case PUBLIC:
return PUBLIC_MODIFIER_PREFIX;
default:
return "";
}
}
protected String qualifyWithPackage(String reference) {
return DOT_JOINER.join(Strings.emptyToNull(packageOf()), reference);
}
}
@Value.Immutable
public static abstract class NameForms extends AbstractNameForms {
/**
* Fully qualified type name
*/
@Override
public String toString() {
return relativeAlreadyQualified()
? relative()
: qualifyWithPackage(relative());
}
public AppliedNameForms applied(String input) {
return ImmutableConstitution.AppliedNameForms.builder()
.forms(this)
.applied(input)
.build();
}
}
@Value.Lazy
public InnerBuilderDefinition innerBuilder() {
return new InnerBuilderDefinition();
}
public final class InnerBuilderDefinition {
public final boolean isAccessibleFields;
public final boolean isPresent;
public final boolean isExtending;
public final boolean isSuper;
public final boolean isInterface;
public final Visibility visibility;
public final @Nullable String simpleName;
public final @Nullable Generics generics;
InnerBuilderDefinition() {
@Nullable TypeElement builderElement = findBuilderElement();
// The following series of checks designed
// to not validate inner builder if it's disabled,
// but at the same time we need such validation
// if we are using "extending" builder which is still allowed
// on demand even if builder feature is disabled
boolean extending = false;
if (builderElement != null) {
extending = isExtending(builderElement);
}
if (builderElement != null && !protoclass().features().builder() && !extending) {
builderElement = null;
}
if (builderElement != null && !isValidInnerBuilder(builderElement)) {
builderElement = null;
}
if (builderElement != null) {
this.isAccessibleFields = AccessibleFieldsMirror.find(builderElement).isPresent();
this.isPresent = true;
this.isInterface = builderElement.getKind() == ElementKind.INTERFACE;
this.isExtending = extending;
this.isSuper = !extending;
this.simpleName = builderElement.getSimpleName().toString();
this.visibility = Visibility.of(builderElement);
this.generics = new Generics(protoclass(), builderElement);
if (isExtending) {
lateValidateExtending(builderElement);
}
if (isSuper) {
lateValidateSuper(builderElement);
}
} else {
this.isAccessibleFields = false;
this.isPresent = false;
this.isInterface = false;
this.isExtending = false;
this.isSuper = false;
this.visibility = Visibility.PRIVATE;
this.simpleName = null;
this.generics = Generics.empty();
}
}
private void lateValidateSuper(TypeElement t) {
List<String> undeclaredParams = Lists.newArrayList();
for (String v : this.generics.vars()) {
if (!generics().hasParameter(v)) {
undeclaredParams.add(v);
}
}
if (!undeclaredParams.isEmpty()) {
protoclass()
.report()
.withElement(t)
.error("Inner type %s%s uses generic parameter %s which are not present in value's declaration: %s",
t.getSimpleName(),
this.generics.args(),
Joiner.on(", ").join(undeclaredParams),
generics());
}
}
private void lateValidateExtending(TypeElement t) {
if (t.getModifiers().contains(Modifier.ABSTRACT)) {
protoclass()
.report()
.withElement(t)
.error("Extending %s shouldn't be abstract, it has to be instantiable",
t.getSimpleName());
}
if (!this.generics.def().equals(generics().def())) {
protoclass()
.report()
.withElement(t)
.error("Inner type %s should have the same type parameters as abstract value type: %s",
t.getSimpleName(),
generics().def());
}
}
private boolean isExtending(TypeElement element) {
if (element.getKind() == ElementKind.CLASS) {
String superclassString = SourceExtraction.getSuperclassString(element);
String rawSuperclass = SourceTypes.extract(superclassString).getKey();
// If we are extending yet to be generated builder, we detect it by having the same name
// as relative name of builder type
return rawSuperclass.endsWith(typeImplementationBuilder().relativeRaw());
}
return false;
}
@Nullable
private TypeElement findBuilderElement() {
Protoclass protoclass = protoclass();
if (!protoclass.kind().isValue()) {
return null;
}
for (Element t : protoclass.sourceElement().getEnclosedElements()) {
ElementKind kind = t.getKind();
if (kind.isClass() || kind.isInterface()) {
String simpleName = t.getSimpleName().toString();
Naming typeInnerBuilderNaming = names().namings.typeInnerBuilder;
if (!typeInnerBuilderNaming.detect(simpleName).isEmpty()) {
return (TypeElement) t;
}
}
}
return null;
}
private boolean isValidInnerBuilder(Element t) {
ElementKind kind = t.getKind();
if (kind != ElementKind.CLASS
&& kind != ElementKind.INTERFACE) {
protoclass()
.report()
.withElement(t)
.warning("Inner type %s is %s - not supported as Builder extend/super type",
t.getSimpleName(),
kind.name().toLowerCase());
return false;
}
Set<Modifier> modifiers = t.getModifiers();
if (!modifiers.contains(Modifier.STATIC)
|| modifiers.contains(Modifier.PRIVATE)) {
protoclass()
.report()
.withElement(t)
.warning("Inner type %s should be static non-private to be supported as Builder extend/super type",
t.getSimpleName());
return false;
}
if (kind == ElementKind.CLASS
&& !hasAccessibleConstructor(t)) {
protoclass()
.report()
.withElement(t)
.warning("%s should have non-private no-argument constructor to be supported as Builder extend/super type",
t.getSimpleName());
return false;
}
return true;
}
private boolean hasAccessibleConstructor(Element type) {
List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
if (constructors.isEmpty()) {
// It is unclear (not checked) if we will have syntethic no-arg constructor
// included, so we will assume no constructor to equate having a single constructors.
return true;
}
for (ExecutableElement c : constructors) {
if (c.getParameters().isEmpty()) {
return !Visibility.of(c).isPrivate();
}
}
return false;
}
}
}