1/*
2 * Copyright (C) 2014 Google, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package dagger.internal.codegen.writer;
17
18import com.google.common.base.Function;
19import com.google.common.base.Joiner;
20import com.google.common.collect.FluentIterable;
21import com.google.common.collect.ImmutableList;
22import com.google.common.collect.ImmutableSet;
23import com.google.common.collect.Iterables;
24import java.io.IOException;
25import java.util.Collections;
26import java.util.Formatter;
27import java.util.Iterator;
28import java.util.Set;
29
30public abstract class Snippet implements HasClassReferences, Writable {
31
32  abstract ImmutableSet<TypeName> types();
33
34  @Override
35  public String toString() {
36    return Writables.writeToString(this);
37  }
38
39  @Override
40  public final Set<ClassName> referencedClasses() {
41    return FluentIterable.from(types())
42        .transformAndConcat(
43            new Function<TypeName, Set<ClassName>>() {
44              @Override
45              public Set<ClassName> apply(TypeName input) {
46                return input.referencedClasses();
47              }
48            })
49        .toSet();
50  }
51
52  private static final class BasicSnippet extends Snippet {
53    final String format;
54    final ImmutableSet<TypeName> types;
55    final ImmutableList<Object> args;
56
57    BasicSnippet(String format, ImmutableSet<TypeName> types, ImmutableList<Object> args) {
58      this.format = format;
59      this.types = types;
60      this.args = args;
61    }
62
63    @Override
64    ImmutableSet<TypeName> types() {
65      return types;
66    }
67
68    @Override
69    public Appendable write(Appendable appendable, Context context) throws IOException {
70      ImmutableList.Builder<Object> formattedArgsBuilder = ImmutableList.builder();
71      for (Object arg : args) {
72        if (arg instanceof Writable) {
73          formattedArgsBuilder.add(((Writable) arg).write(new StringBuilder(), context).toString());
74        } else {
75          formattedArgsBuilder.add(arg);
76        }
77      }
78
79      @SuppressWarnings("resource") // intentionally don't close the formatter
80      Formatter formatter = new Formatter(appendable);
81      formatter.format(format, Iterables.toArray(formattedArgsBuilder.build(), Object.class));
82
83      return appendable;
84    }
85  }
86
87  private static final class CompoundSnippet extends Snippet {
88    final String joinToken;
89    final ImmutableList<Snippet> snippets;
90
91    CompoundSnippet(String joinToken, ImmutableList<Snippet> snippets) {
92      this.joinToken = joinToken;
93      this.snippets = snippets;
94    }
95
96    @Override
97    ImmutableSet<TypeName> types() {
98      return FluentIterable.from(snippets)
99          .transformAndConcat(
100              new Function<Snippet, Iterable<TypeName>>() {
101                @Override
102                public Iterable<TypeName> apply(Snippet input) {
103                  return input.types();
104                }
105              })
106          .toSet();
107    }
108
109    @Override
110    public Appendable write(Appendable appendable, Context context) throws IOException {
111      Iterator<Snippet> snippetIterator = snippets.iterator();
112      if (snippetIterator.hasNext()) {
113        Snippet firstSnippet = snippetIterator.next();
114        firstSnippet.write(appendable, context);
115        while (snippetIterator.hasNext()) {
116          Snippet nextSnippet = snippetIterator.next();
117          appendable.append(joinToken);
118          nextSnippet.write(appendable, context);
119        }
120      }
121      return appendable;
122    }
123  }
124
125  public static Snippet format(String format, Object... args) {
126    ImmutableSet.Builder<TypeName> types = ImmutableSet.builder();
127    for (Object arg : args) {
128      if (arg instanceof Snippet) {
129        types.addAll(((Snippet) arg).types());
130      }
131      if (arg instanceof TypeName) {
132        types.add((TypeName) arg);
133      }
134      if (arg instanceof HasTypeName) {
135        types.add(((HasTypeName) arg).name());
136      }
137    }
138    return new BasicSnippet(format, types.build(), ImmutableList.copyOf(args));
139  }
140
141  public static Snippet format(String format, Iterable<? extends Object> args) {
142    return format(format, Iterables.toArray(args, Object.class));
143  }
144
145  public static Snippet memberSelectSnippet(Iterable<? extends Object> selectors) {
146    return format(Joiner.on('.').join(Collections.nCopies(Iterables.size(selectors), "%s")),
147        selectors);
148  }
149
150  public static Snippet nullCheck(Object thingToCheck) {
151    return format("if (%s == null) { throw new NullPointerException(); } ", thingToCheck);
152  }
153
154  public static Snippet nullCheck(Object thingToCheck, String message) {
155    return format("if (%s == null) { throw new NullPointerException(%s); } ",
156        thingToCheck,
157        StringLiteral.forValue(message));
158  }
159
160  public static Snippet makeParametersSnippet(Iterable<Snippet> parameterSnippets) {
161    return join(", ", parameterSnippets);
162  }
163
164  /**
165   * A snippet that concatenates its arguments with each snippet separated by a new line.
166   */
167  public static Snippet concat(Iterable<Snippet> snippets) {
168    return join("\n", snippets);
169  }
170
171  /**
172   * A snippet that joins its arguments with {@code joiner}.
173   */
174  public static Snippet join(String joinToken, Iterable<Snippet> snippets) {
175    return new CompoundSnippet(joinToken, ImmutableList.copyOf(snippets));
176  }
177}
178