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  */
16 package dagger.internal.codegen.writer;
17 
18 import com.google.common.base.Function;
19 import com.google.common.base.Joiner;
20 import com.google.common.collect.FluentIterable;
21 import com.google.common.collect.ImmutableList;
22 import com.google.common.collect.ImmutableSet;
23 import com.google.common.collect.Iterables;
24 import java.io.IOException;
25 import java.util.Collections;
26 import java.util.Formatter;
27 import java.util.Iterator;
28 import java.util.Set;
29 
30 public abstract class Snippet implements HasClassReferences, Writable {
31 
types()32   abstract ImmutableSet<TypeName> types();
33 
34   @Override
toString()35   public String toString() {
36     return Writables.writeToString(this);
37   }
38 
39   @Override
referencedClasses()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