| /** |
| * 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; |
| } |
| |
| } |