/*
Copyright 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.generator.processor;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import org.immutables.generator.processor.ImmutableTrees.SimpleAccessExpression;
import org.immutables.generator.processor.ImmutableTrees.AssignGenerator;
import org.immutables.generator.processor.ImmutableTrees.ForStatement;
import org.immutables.generator.processor.ImmutableTrees.Identifier;
import org.immutables.generator.processor.ImmutableTrees.InvokableDeclaration;
import org.immutables.generator.processor.ImmutableTrees.InvokeStatement;
import org.immutables.generator.processor.ImmutableTrees.LetStatement;
import org.immutables.generator.processor.ImmutableTrees.Template;
import org.immutables.generator.processor.ImmutableTrees.TextLine;
import org.immutables.generator.processor.ImmutableTrees.Unit;
import org.immutables.generator.processor.ImmutableTrees.ValueDeclaration;
final class Inliner {
private Inliner() {}
private final Map<Trees.Identifier, InlinedStatementCreator> inlinables = Maps.newHashMap();
public static Unit optimize(Unit unit) {
return new Inliner().inline(unit);
}
private Unit inline(Unit unit) {
new Finder().toUnit(unit);
return new Weaver().toUnit(unit);
}
private static class InlinedStatementCreator extends TreesTransformer {
private final Template inlinable;
private final int uniqueSuffix;
private final Set<Trees.Identifier> remapped = Sets.newHashSet();
InlinedStatementCreator(Template inlinable) {
this.uniqueSuffix = System.identityHashCode(inlinable);
this.inlinable = inlinable;
for (Trees.Parameter p : inlinable.declaration().parameters()) {
remapped.add(p.name());
}
}
ForStatement inlined(List<Trees.Expression> params, Iterable<? extends Trees.TemplatePart> bodyParts) {
ForStatement.Builder builder = ForStatement.builder()
.useForAccess(false)
.useDelimit(false);
Iterator<Trees.Parameter> formals = inlinable.declaration().parameters().iterator();
for (Trees.Expression argument : params) {
Trees.Parameter formal = formals.next();
builder.addDeclaration(
AssignGenerator.builder()
.declaration(declarationFor(formal))
.from(argument)
.build());
}
addBodyIfNecessary(builder, params, bodyParts);
builder.addAllParts(asTemplatePartsElements(inlinable, inlinable.parts()));
return builder.build();
}
private ValueDeclaration declarationFor(Trees.Parameter formalParameter) {
return ValueDeclaration.builder()
.type(formalParameter.type())
.name(remappedIdentifier(formalParameter.name()))
.build();
}
private void addBodyIfNecessary(
ForStatement.Builder builder,
List<Trees.Expression> params,
Iterable<? extends Trees.TemplatePart> bodyParts) {
// body goes as one special parameter, don't handle other mismatches
if (Iterables.isEmpty(bodyParts)) {
return;
}
Preconditions.checkState(inlinable.declaration().parameters().size() == params.size() + 1);
Trees.Parameter lastParameter = Iterables.getLast(inlinable.declaration().parameters());
LetStatement.Builder letBuilder = LetStatement.builder()
.addAllParts(bodyParts)
.declaration(InvokableDeclaration.builder()
.name(remappedIdentifier(lastParameter.name()))
.build());
remapped.add(lastParameter.name());
builder.addParts(letBuilder.build());
}
@Override
public SimpleAccessExpression toSimpleAccessExpression(SimpleAccessExpression value) {
final Trees.Identifier topAccessIdentifier = value.path().get(0);
if (remapped.contains(topAccessIdentifier)) {
return new TreesTransformer() {
@Override
public Identifier toIdentifier(Identifier value) {
return topAccessIdentifier == value
? remappedIdentifier(value)
: value;
}
}.toSimpleAccessExpression(value);
}
return value;
}
protected Identifier remappedIdentifier(Trees.Identifier value) {
return Identifier.of(
value.value()
+ "_" + inlinable.declaration().name().value()
+ "_" + uniqueSuffix);
}
}
final class Finder extends TreesTransformer {
private boolean inlinable;
@Override
public Template toTemplate(Template value) {
if (value.isPublic()) {
return value;
}
inlinable = true;
asTemplatePartsElements(value, value.parts());
if (inlinable) {
inlinables.put(value.declaration().name(), new InlinedStatementCreator(value));
}
return value;
}
@Override
public InvokableDeclaration toInvokableDeclaration(InvokableDeclaration value) {
inlinable = false;
return value;
}
@Override
public ValueDeclaration toValueDeclaration(ValueDeclaration value) {
inlinable = false;
return value;
}
@Override
public TextLine toTextLine(TextLine value) {
if (value.newline()) {
inlinable = false;
}
return value;
}
}
final class Weaver extends TreesTransformer {
@Override
protected Iterable<Trees.UnitPart> asUnitPartsElements(Unit value, List<Trees.UnitPart> parts) {
// TODO decide if we need to remove inlined completely
// could be referenced by outer templates
// return super.transformUnitListParts(context, value, parts);
return super.asUnitPartsElements(value, inlinedRemoved(parts));
}
private List<Trees.UnitPart> inlinedRemoved(List<Trees.UnitPart> parts) {
List<Trees.UnitPart> newParts = Lists.newArrayListWithCapacity(parts.size());
for (Trees.UnitPart p : parts) {
if (!inlinables.containsKey(p)) {
newParts.add(p);
}
}
return newParts;
}
@Override
protected Trees.TemplatePart asTemplatePart(InvokeStatement value) {
@Nullable InlinedStatementCreator creator = tryGetInlinable(value);
if (creator != null) {
return creator.inlined(value.params(), value.parts());
}
return value;
}
private @Nullable InlinedStatementCreator tryGetInlinable(InvokeStatement invoke) {
Trees.Expression access = invoke.access();
if (access instanceof SimpleAccessExpression) {
SimpleAccessExpression ref = (SimpleAccessExpression) access;
if (ref.path().size() == 1) {
Trees.Identifier identifier = ref.path().get(0);
return inlinables.get(identifier);
}
}
return null;
}
}
}