blob: 1265899a5b90b0d7c8bbb9d15f59da00c8599d14 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.wave.pst;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import org.antlr.stringtemplate.StringTemplate;
import org.antlr.stringtemplate.StringTemplateGroup;
import org.apache.wave.pst.model.Message;
import org.apache.wave.pst.model.MessageProperties;
import org.apache.wave.pst.style.Styler;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
/**
* PST stands for protobuf-stringtemplate.
*
* The tool allows arbitrary code generation given a series of string
* templates, each passed the description of a protocol buffer file. This allows
* protobuf- based file generation beyond what existing protocol compilers
* (protoc, protostuff, etc) are capable of (without modification), using the
* convenience and safety of <a href="http://stringtemplate.org">string
* template</a>.
*
* A number of sample templates are bundles (in templates), so see these as
* examples. These templates give a complete client/server JSON message stack:
* <ul>
* <li><em>message</em> is a common interface.</li>
* <li><em>messageTestImpl</em> is a simple/pure Java in-memory implementation
* of the interface, for testing.</li>
* <li><em>messagePojoImpl</em> is a like messageTestImpl with JSON
* serialization and deserialization using the Gson library.</li>
* <li><em>messageServerImpl</em> is a protobuf-backed implementation, useful
* for a multi-server environment where the efficient serialization of protocol
* buffers is an advantage. JSON is also supported.</li>
* <li><em>messageClientImpl</em> is an efficent javascript implementation for
* use with GWT.</li>
* </ul>
*
* There is no particular reason why PST can only be used to generate Java, for
* example, a pure JS rather than GWT implementation of the client JSON message
* component could be generated[1].
*
* PST is implemented using the protocol buffer reflection generated by protoc
* alongside the actual Message classes it generates; these are converted into
* simple Java model objects with simple accessors suitable for accessing from
* stringtemplate.
*
* The code generated by stringtemplate is then post-processed using a simple
* custom code formatter since the output from stringtemplate can be hard to
* humanly read (e.g. the indentation is unpredictable).
*
* [1] although, currently it is hardcoded in PST to generate .java files, and
* the model has Java-centric methods. The code formatter also assumes that
* it is run over a Java file. These all could be easily modified, however.
*
* @author kalman@google.com (Benjamin Kalman)
*/
public final class Pst {
private final File outputDir;
private final FileDescriptor fd;
private final Styler styler;
private final Iterable<File> templates;
private final boolean saveBackups;
private final boolean useInt52;
/**
* @param outputDir the base directory to write the generated files
* @param fd the {@link FileDescriptor} of the protobuf to use (i.e. pass to
* each string template)
* @param styler the code styler to post-process generated code with
* @param templates the collection of string templates to use
* @param saveBackups whether to save intermediate generated files
* @param useInt52 whether we use doubles to serialize 64-bit integers
*/
public Pst(File outputDir, FileDescriptor fd, Styler styler, Iterable<File> templates,
boolean saveBackups, boolean useInt52) {
this.outputDir = checkNotNull(outputDir, "outputDir cannot be null");
this.fd = checkNotNull(fd, "fd cannot be null");
this.styler = checkNotNull(styler, "styler cannot be null");
this.templates = checkNotNull(templates, "templates cannot be null");
this.saveBackups = saveBackups;
this.useInt52 = useInt52;
}
/**
* Runs the code generation for all templates.
*/
public void run() throws PstException {
List<PstException.TemplateException> exceptions = Lists.newArrayList();
for (File template : templates) {
try {
MessageProperties properties = createProperties(template);
String groupName = stripSuffix(".st", template.getName());
String templateName = properties.hasTemplateName() ?
properties.getTemplateName() : "";
StringTemplateGroup group = new StringTemplateGroup(groupName + "Group", dir(template));
StringTemplate st = group.getInstanceOf(groupName);
for (Descriptor messageDescriptor : fd.getMessageTypes()) {
Message message = new Message(messageDescriptor, templateName, properties);
st.reset();
st.setAttribute("m", message);
write(st, new File(
outputDir.getPath() + File.separator +
message.getFullJavaType().replace('.', File.separatorChar) + "." +
(properties.hasFileExtension() ? properties.getFileExtension() : "java")));
}
} catch (Exception e) {
exceptions.add(new PstException.TemplateException(template.getPath(), e));
}
}
if (!exceptions.isEmpty()) {
throw new PstException(exceptions);
}
}
/**
* @return the path to the directory which contains a file, or just the path
* to the file itself if it's already a directory
*/
private String dir(File f) {
return f.isDirectory()
? f.getPath()
: (Strings.isNullOrEmpty(f.getParent()) ? "." : f.getParent());
}
private String stripSuffix(String suffix, String s) {
return s.endsWith(suffix) ? s.substring(0, s.length() - suffix.length()) : s;
}
private void write(StringTemplate st, File output) throws IOException {
output.getParentFile().mkdirs();
BufferedWriter writer = new BufferedWriter(new FileWriter(output));
try {
writer.write(st.toString());
} finally {
try {
writer.close();
} catch (IOException e) {
// If another exception is already propagating, we don't
// want to throw a secondary one.
// This means that exceptions on close() are ignored,
// but what could usefully be done for a close()
// exception anyway?
}
}
styler.style(output, saveBackups);
}
private MessageProperties createProperties(File template)
throws FileNotFoundException, IOException {
File propertiesFile =
new File(template.getParentFile().getPath() + File.separator + "properties");
MessageProperties properties = propertiesFile.exists()
? MessageProperties.createFromFile(propertiesFile) : MessageProperties.createEmpty();
properties.setUseInt52(useInt52);
return properties;
}
}