http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/model/Field.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/apache/wave/pst/model/Field.java b/pst/src/main/java/org/apache/wave/pst/model/Field.java new file mode 100644 index 0000000..2477566 --- /dev/null +++ b/pst/src/main/java/org/apache/wave/pst/model/Field.java @@ -0,0 +1,283 @@ +/** + * 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.model; + +import com.google.protobuf.Descriptors.FieldDescriptor; + +import org.apache.wave.pst.protobuf.Extensions; + +/** + * Wraps a {@link FieldDescriptor} with methods suitable for stringtemplate. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class Field { + + private final FieldDescriptor field; + private final Type type; + private final MessageProperties properties; + + public Field(FieldDescriptor field, Type type, MessageProperties properties) { + this.field = field; + this.type = type; + this.properties = properties; + } + + /** + * Returns the type of the field as the Java type, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "int"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as the Java type + */ + public String getJavaType() { + return type.getJavaType(isInt52()); + } + + /** + * Returns the type of the field as the Java type capitalized, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "Int"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as the Java type + */ + public String getCapJavaType() { + return type.getCapJavaType(isInt52()); + } + + /** + * Returns the type of the field as the boxed Java type, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "Integer"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as a boxed Java type + */ + public String getBoxedJavaType() { + return type.getBoxedJavaType(isInt52()); + } + + /** + * Returns the message type of the field without template suffix, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = undefined</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = undefined</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = undefined</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = + * "Address" (regardless of template name)</li> + * </ul> + * + * @return the message type of the field without template suffix + */ + public String getMessageType() { + return type.getMessage().getName(); + } + + /** + * Gets the type of this field. + * + * @return the type of this field. + */ + public Type getType() { + return type; + } + + /** + * Returns the name of the field as uncapitalizedCamelCase, for example + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "firstName"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "age"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.lucky_numbers = "luckyNumbers"</li> + * </ul> + * + * @return the name of the field as uncapitalizedCamelCase + */ + public String getName() { + return Util.uncapitalize(getCapName()); + } + + /** + * Returns the name of the field as CapitalizedCamelCase, for example + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "FirstName"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "age"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.lucky_numbers = "LuckyNumbers"</li> + * </ul> + * + * @return the name of the field as CapitalizedCamelCase + */ + public String getCapName() { + StringBuilder result = new StringBuilder(); + for (String s : getNameParts()) { + result.append(Util.capitalize(s)); + } + return result.toString(); + } + + private String[] getNameParts() { + // Assumes that the field is separated by underscores... not sure if this + // is always the case. + return field.getName().split("_"); + } + + /** + * Returns the number of the field, for example + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = 1</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = 4</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.lucky_numbers = 5</li> + * </ul> + * + * @return the number of the field + */ + public int getNumber() { + return field.getNumber(); + } + + /** + * Gets the default value of the field (null for objects, empty strings/arrays, zero, false, etc). + * + * @return the "default value" of the field + */ + public String getDefaultValue() { + return type.getDefaultValue(); + } + + /** + * Gets the name of a Java getter for this field. + * + * @return the name of a Java getter for this field. + */ + public String getGetter() { + return "get" + getCapName(); + } + + /** + * Gets the name of a Java setter for this field. + * + * @return the name of a Java getter for this field. + */ + public String getSetter() { + return "set" + getCapName(); + } + + /** + * Gets whether the field is of type int52. This means that although the + * field's native type is int64, only 52 bits of information are used. + * + * @return whether the field is a 52-bit integer + */ + public boolean isInt52() { + return properties.getUseInt52() // + && field.getOptions().hasExtension(Extensions.int52) + && field.getOptions().getExtension(Extensions.int52); + } + + /** + * Gets whether the field is of type long (int64). This means that the field + * may use up to 64 bits of information. + * + * @return whether the field is a long (64-bit integer) + */ + public boolean isLong() { + return field.getJavaType() == FieldDescriptor.JavaType.LONG && !isInt52(); + } + + // + // These map directly to the .proto definitions (except for isPrimitive, but that's pretty + // self explanatory). + // + + /** + * @return whether the field is required + */ + public boolean isRequired() { + return field.isRequired(); + } + + /** + * @return whether the field is optional + */ + public boolean isOptional() { + return field.isOptional(); + } + + /** + * @return whether the field is repeated + */ + public boolean isRepeated() { + return field.isRepeated(); + } + + /** + * @return whether the field is a message + */ + public boolean isMessage() { + return type.isMessage(); + } + + /** + * @return whether the field is an enum + */ + public boolean isEnum() { + return type.isEnum(); + } + + /** + * @return whether the field type is a Java primitive + */ + public boolean isPrimitive() { + return type.isPrimitive(); + } + + /** + * @return whether the field type is a data blob. + */ + public boolean isBlob() { + return type.isBlob(); + } + + /** + * @return whether the field type is a Java primitive and not repeated + */ + public boolean isPrimitiveAndNotRepeated() { + // NOTE: If stringtemplate could handle statements like + // $if (f.primitive && !f.repeated)$ + // then this method would be unnecessary. However, from what I can tell, it can't. + return isPrimitive() && !isRepeated(); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/model/Message.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/apache/wave/pst/model/Message.java b/pst/src/main/java/org/apache/wave/pst/model/Message.java new file mode 100644 index 0000000..75e2483 --- /dev/null +++ b/pst/src/main/java/org/apache/wave/pst/model/Message.java @@ -0,0 +1,343 @@ +/** + * 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.model; + + import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor.JavaType; + +import java.util.Collection; +import java.util.Deque; +import java.util.List; + +/** + * Wraps a {@link Descriptor} with methods suitable for stringtemplate. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class Message { + + private final Descriptor descriptor; + private final String templateName; + private final MessageProperties extraProperties; + + // Lazily created. + private List<Field> fields = null; + private List<Message> messages = null; + private List<ProtoEnum> enums = null; + private List<Message> referencedMessages = null; + private List<ProtoEnum> referencedEnums = null; + private String fullName; + private String fullJavaType; + + public Message(Descriptor descriptor, String templateName, MessageProperties extraProperties) { + this.descriptor = descriptor; + this.templateName = templateName; + this.extraProperties = extraProperties; + } + + /** + * Returns the short name of the Java type of this message, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = "Person"</li> + * </ul> + * + * @return the name of the protocol buffer message + */ + public String getName() { + return descriptor.getName(); + } + + /** + * Returns the short name of Java type being generated. For example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = <ul> + * <li>"PersonMessage" (for template name "message")</li> + * <li>"PersonMessageServerImpl" (for template name "messageServerImpl")</li></ul> + * </li> + * </ul> + * + * @return the name of the Java message + */ + public String getJavaType() { + return descriptor.getName() + Util.capitalize(templateName); + } + + /** + * Returns the full name of the this message in abstract Java space. For + * example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = <ul> + * <li>"org.waveprotocol.pst.examples.Person" + * (for template name "Message" and package suffix "dto")</li></ul> + * </li> + * </ul> + * + * @return the name of the protocol buffer message + */ + public String getFullName() { + if (fullName == null) { + fullName = getFullName(false); + } + return fullName; + } + + /** + * Returns the full name of the Java type of this message, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = <ul> + * <li>"org.waveprotocol.pst.examples.dto.PersonMessage" + * (for template name "Message" and package suffix "dto")</li> + * <li>"org.waveprotocol.pst.examples.impl.PersonImpl" + * (for template name "Impl" and package suffix "impl")</li></ul> + * </li> + * </ul> + * + * @return the name of the protocol buffer message + */ + public String getFullJavaType() { + if (fullJavaType == null) { + fullJavaType = getFullName(true); + } + return fullJavaType; + } + + /** + * Gets the fully-qualified name of this message. + * + * @param covariant if true, the name refers to the Java type being generated + * for this message. Otherwise, the name refers to a + * template-independent Java type, which may or may not exist. This is + * intended to be used so that the generated Java type for this message + * can refer to other Java types derived from this message. + * @return the fully-qualified name of this message. + */ + private String getFullName(boolean covariant) { + String prefix; + if (descriptor.getContainingType() != null) { + prefix = adapt(descriptor.getContainingType()).getFullName(covariant); + } else { + prefix = covariant ? getPackage() : getPackageBase(); + } + + return prefix + "." + (covariant ? getJavaType() : getName()); + } + + /** + * Returns the package of the Java messageas the base plus the suffix + * components of the package, for example given org.waveprotocol.pst.examples.Example1.Person: + * <ul> + * <li>Message = "org.waveprotocol.pst.examples"</li> + * <li>MessageServerImpl (package suffix "server") = "org.waveprotocol.pst.examples.server"</li> + * <li>MessageClientImpl (package suffix "client") = "org.waveprotocol.pst.examples.client"</li> + * </ul> + * + * @return the Java package of the message + */ + public String getPackage() { + String suffix = getPackageSuffix(); + return getPackageBase() + (!Strings.isNullOrEmpty(suffix) ? "." + suffix : ""); + } + + /** + * Returns the base component of the Java message package, for example, given + * org.waveprotocol.pst.examples.Example1.Person: + * <ul> + * <li>Message = "org.waveprotocol.pst.examples"</li> + * <li>MessageServerImpl (package suffix "server") = "org.waveprotocol.pst.examples"</li> + * </ul> + * + * @return the base component of the Java package + */ + public String getPackageBase() { + String javaPackage = descriptor.getFile().getOptions().getJavaPackage(); + if (Strings.isNullOrEmpty(javaPackage)) { + javaPackage = descriptor.getFile().getPackage(); + } + return javaPackage; + } + + /** + * Returns the suffix component of the Java message package, as configured in + * the message's properties file, for example: + * <ul> + * <li>Message = null</li> + * <li>MessageServerImpl = "server"</li> + * <li>MessageClientImpl = "client"</li> + * </ul> + */ + public String getPackageSuffix() { + return extraProperties.getPackageSuffix(); + } + + /** + * @return the filename of the protocol buffer (.proto) file where the message + * is defined + */ + public String getFilename() { + return descriptor.getFile().getName(); + } + + /** + * Returns the qualified type of the protobuf message, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = + * "org.waveprotocol.pst.examples.Example1.Person"</li> + * </ul> + * + * @return the full type of the protocol buffer message + */ + public String getProtoType() { + Deque<String> scopes = Lists.newLinkedList(); + for (Descriptor message = descriptor; message != null; message = message.getContainingType()) { + scopes.push(message.getName()); + } + scopes.push(descriptor.getFile().getOptions().getJavaOuterClassname()); + scopes.push(getPackageBase()); + return Joiner.on('.').join(scopes); + } + + /** + * @return the fields of the message + */ + public List<Field> getFields() { + if (fields == null) { + ImmutableList.Builder<Field> builder = ImmutableList.builder(); + for (FieldDescriptor fd : descriptor.getFields()) { + builder.add(new Field(fd, new Type(fd, templateName, extraProperties), extraProperties)); + } + fields = builder.build(); + } + return fields; + } + + /** + * @return the set of all messages referred to be this message and its nested + * messages. Message references are due to message-typed fields. + */ + public List<Message> getReferencedMessages() { + if (referencedMessages == null) { + referencedMessages = Lists.newArrayList(); + for (Descriptor d : collectMessages(descriptor, Sets.<Descriptor>newLinkedHashSet())) { + referencedMessages.add(adapt(d)); + } + } + return referencedMessages; + } + + /** + * @return the set of all enums referred to be this message and its nested + * messages. Enum references are due to message-typed fields. + */ + public List<ProtoEnum> getReferencedEnums() { + if (referencedEnums == null) { + referencedEnums = Lists.newArrayList(); + for (EnumDescriptor d : collectEnums(descriptor, Sets.<EnumDescriptor> newLinkedHashSet())) { + referencedEnums.add(adapt(d)); + } + } + return referencedEnums; + } + + /** + * Collects messages referred to by a message and its nested messages. + * + * @return {@code referenced} + */ + private static Collection<Descriptor> collectMessages( + Descriptor message, Collection<Descriptor> referenced) { + for (FieldDescriptor fd : message.getFields()) { + if (fd.getJavaType() == JavaType.MESSAGE) { + referenced.add(fd.getMessageType()); + } + } + for (Descriptor nd : message.getNestedTypes()) { + collectMessages(nd, referenced); + } + return referenced; + } + + /** + * Collects enums referred to by a message and its nested messages. + * + * @return {@code referenced} + */ + private static Collection<EnumDescriptor> collectEnums( + Descriptor d, Collection<EnumDescriptor> referenced) { + for (FieldDescriptor fd : d.getFields()) { + if (fd.getJavaType() == JavaType.ENUM) { + referenced.add(fd.getEnumType()); + } + } + for (Descriptor nd : d.getNestedTypes()) { + collectEnums(nd, referenced); + } + return referenced; + } + + /** + * @return the nested messages of the message + */ + public List<Message> getNestedMessages() { + if (messages == null) { + ImmutableList.Builder<Message> builder = ImmutableList.builder(); + for (Descriptor d : descriptor.getNestedTypes()) { + builder.add(adapt(d)); + } + messages = builder.build(); + } + return messages; + } + + /** + * @return the nested enums of the message + */ + public List<ProtoEnum> getNestedEnums() { + if (enums == null) { + ImmutableList.Builder<ProtoEnum> builder = ImmutableList.builder(); + for (EnumDescriptor ed : descriptor.getEnumTypes()) { + builder.add(adapt(ed)); + } + enums = builder.build(); + } + return enums; + } + + /** + * @return whether this is an inner class + */ + public boolean isInner() { + return descriptor.getContainingType() != null; + } + + private Message adapt(Descriptor d) { + return new Message(d, templateName, extraProperties); + } + + private ProtoEnum adapt(EnumDescriptor d) { + return new ProtoEnum(d, templateName, extraProperties); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/model/MessageProperties.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/apache/wave/pst/model/MessageProperties.java b/pst/src/main/java/org/apache/wave/pst/model/MessageProperties.java new file mode 100644 index 0000000..782d639 --- /dev/null +++ b/pst/src/main/java/org/apache/wave/pst/model/MessageProperties.java @@ -0,0 +1,112 @@ +/** + * 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.model; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Properties; + +/** + * Container for the Properties of a message. + * + * @author [email protected] + */ +public final class MessageProperties { + + private static final String PACKAGE_SUFFIX = "package.suffix"; + private static final String FILE_EXTENSION = "file.extension"; + private static final String TEMPLATE_NAME = "template.name"; + + private final Properties properties; + private boolean useInt52; + + private MessageProperties(Properties properties) { + this.properties = properties; + } + + public static MessageProperties createFromFile(File propertiesFile) throws FileNotFoundException, + IOException { + Properties properties = new Properties(); + properties.load(new FileReader(propertiesFile)); + return new MessageProperties(properties); + } + + public static MessageProperties createEmpty() { + return new MessageProperties(new Properties()); + } + + /** + * @return the package suffix, or null if one isn't specified. + */ + public String getPackageSuffix() { + return properties.getProperty(PACKAGE_SUFFIX); + } + + /** + * @return whether a package suffix has been specified. + */ + public boolean hasPackageSuffix() { + return getPackageSuffix() != null; + } + + /** + * @return the file extension, or null if it isn't specified. + */ + public String getFileExtension() { + return properties.getProperty(FILE_EXTENSION); + } + + /** + * @return whether a file extension has been specified. + */ + public boolean hasFileExtension() { + return getFileExtension() != null; + } + + /** + * @return the template name, or null if it isn't specified. + */ + public String getTemplateName() { + return properties.getProperty(TEMPLATE_NAME); + } + + /** + * @return whether a template name has been specified. + */ + public boolean hasTemplateName() { + return getTemplateName() != null; + } + + /** + * Sets the global int52 type property + */ + public void setUseInt52(boolean useInt52) { + this.useInt52 = useInt52; + } + + /** + * @return the int52 type or null if it isn't specified. + */ + public boolean getUseInt52() { + return useInt52; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/model/ProtoEnum.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/apache/wave/pst/model/ProtoEnum.java b/pst/src/main/java/org/apache/wave/pst/model/ProtoEnum.java new file mode 100644 index 0000000..abc5f8a --- /dev/null +++ b/pst/src/main/java/org/apache/wave/pst/model/ProtoEnum.java @@ -0,0 +1,140 @@ +/** + * 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.model; + +import com.google.common.collect.Lists; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; + +import java.util.List; + +/** + * Wraps a {@link EnumDescriptor} with methods suitable for stringtemplate. + * + * Called ProtoEnum rather than Enum to avoid java.lang namespace conflict. + * + * @author [email protected] (Benjamnin Kalman) + */ +public final class ProtoEnum { + + private final EnumDescriptor descriptor; + private final String templateName; + private final MessageProperties extra; + + private String fullName; + private String fullJavaType; + + public ProtoEnum(EnumDescriptor descriptor, String templateName, MessageProperties extra) { + this.descriptor = descriptor; + this.templateName = templateName; + this.extra = extra; + } + + /** + * Returns the enum, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender = "Gender"</li> + * </ul> + * + * @return the name of the enum + */ + public String getName() { + return descriptor.getName(); + } + + /** + * Returns the short name of the Java type generated for this enum, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender = "Gender"</li> + * </ul> + * + * @return the name of the java type of the enum + */ + public String getJavaType() { + return getName(); + } + + /** + * Returns the fully-qualified name of the enum in abstract space. For + * example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender = + * "org.waveprotocol.pst.examples.Person.Gender"</li> + * </ul> + * + * @return the name of the enum + */ + public String getFullName() { + if (fullName == null) { + fullName = getContainingMessage().getFullName() + "." + getName(); + } + return fullName; + } + + /** + * Returns the fully-qualified name of the Java type for this enum. + * example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender = + * "org.waveprotocol.pst.examples.Person.Gender"</li> + * </ul> + * + * @return the name of the enum + */ + public String getFullJavaType() { + if (fullJavaType == null) { + fullJavaType = getContainingMessage().getFullJavaType() + "." + getName(); + } + return fullJavaType; + } + + /** + * Returns the qualified type of the protobuf enum, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person = + * "org.waveprotocol.pst.examples.Example1.Person"</li> + * </ul> + * + * @return the full type of the protocol buffer enum + */ + public String getProtoType() { + return getContainingMessage().getProtoType() + "." + getName(); + } + + /** + * Returns the enum values, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.Gender = [MALE, FEMALE, OTHER]</li> + * </ul> + * + * @return the enum values + */ + public List<EnumValue> getValues() { + List<EnumValue> enums = Lists.newArrayList(); + for (EnumValueDescriptor evd : descriptor.getValues()) { + enums.add(new EnumValue(evd)); + } + return enums; + } + + private Message getContainingMessage() { + return new Message(descriptor.getContainingType(), templateName, extra); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/model/Type.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/apache/wave/pst/model/Type.java b/pst/src/main/java/org/apache/wave/pst/model/Type.java new file mode 100644 index 0000000..7fcb7b8 --- /dev/null +++ b/pst/src/main/java/org/apache/wave/pst/model/Type.java @@ -0,0 +1,244 @@ +/** + * 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.model; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; + +/** + * Wraps a {@link FieldDescriptor} to expose type-only information for + * stringtemplate. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class Type { + + private final FieldDescriptor field; + private final String templateName; + private final MessageProperties extraProperties; + private Message messageType; + + public Type(FieldDescriptor field, String templateName, MessageProperties extraProperties) { + this.field = field; + this.templateName = templateName; + this.extraProperties = extraProperties; + } + + /** + * Returns the type of the field as the Java type, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "int"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as the Java type + */ + public String getJavaType(boolean hasInt52Ext) { + switch (field.getJavaType()) { + case BOOLEAN: + return "boolean"; + case BYTE_STRING: + return "Blob"; + case DOUBLE: + return "double"; + case ENUM: + return field.getEnumType().getName(); + case FLOAT: + return "float"; + case INT: + return "int"; + case LONG: + return hasInt52Ext && extraProperties.getUseInt52() ? "double" : "long"; + case MESSAGE: + return getMessage().getJavaType(); + case STRING: + return "String"; + default: + throw new UnsupportedOperationException("Unsupported field type " + field.getJavaType()); + } + } + + /** + * Returns the type of the field as the Java type capitalized, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "Int"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as the Java type + */ + public String getCapJavaType(boolean hasInt52Ext) { + return Util.capitalize(getJavaType(hasInt52Ext)); + } + + /** + * Returns the type of the field as the boxed Java type, for example: + * <ul> + * <li>org.waveprotocol.pst.examples.Example1.Person.first_name = "String"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.age = "Integer"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.gender = "Gender"</li> + * <li>org.waveprotocol.pst.examples.Example1.Person.address = <ul> + * <li>"AddressMessage" (if template name is "message")</li> + * <li>"AddressMessageServerImpl" (if template name is "messageServerImpl")</li></ul></li> + * </ul> + * + * @return the type of the field as a boxed Java type + */ + public String getBoxedJavaType(boolean hasInt52Ext) { + switch (field.getJavaType()) { + case BOOLEAN: + return "Boolean"; + case DOUBLE: + return "Double"; + case FLOAT: + return "Float"; + case INT: + return "Integer"; + case LONG: + return hasInt52Ext && extraProperties.getUseInt52() ? "Double" : "Long"; + default: + return getJavaType(hasInt52Ext); + } + } + + /** + * Gets the default value of the field (null for objects, zero, false, etc). + * + * @return the "default value" of the field + */ + public String getDefaultValue() { + switch (field.getJavaType()) { + case BOOLEAN: + return "false"; + case BYTE_STRING: + return "null"; + case DOUBLE: + return "0.0"; + case ENUM: + return field.getEnumType().getName() + ".UNKNOWN"; + case FLOAT: + return "0.0f"; + case INT: + return "0"; + case LONG: + return "0L"; + case MESSAGE: + return "null"; + case STRING: + return "null"; + default: + throw new UnsupportedOperationException("Unsupported field type " + field.getJavaType()); + } + } + + /** + * @return this type as a message. + */ + public Message getMessage() { + if (messageType == null) { + messageType = adapt(field.getMessageType()); + } + return messageType; + } + + /** + * @return whether the field is a message + */ + public boolean isMessage() { + return field.getType().equals(FieldDescriptor.Type.MESSAGE); + } + + /** + * @return whether the field is an enum + */ + public boolean isEnum() { + return field.getType().equals(FieldDescriptor.Type.ENUM); + } + + /** + * @return whether the field is a byte string. + */ + public boolean isBlob() { + return field.getType().equals(FieldDescriptor.Type.BYTES); + } + + /** + * @return whether the field type is a Java primitive + */ + public boolean isPrimitive() { + switch (field.getJavaType()) { + case BOOLEAN: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + return true; + default: + return false; + } + } + + private Message adapt(Descriptor d) { + return new Message(d, templateName, extraProperties); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof Type) { + Type t = (Type) o; + if (field.getType() == t.field.getType()) { + switch (field.getType()) { + case MESSAGE: + return field.getMessageType().equals(t.field.getMessageType()); + case ENUM: + return field.getEnumType().equals(t.field.getEnumType()); + default: + return true; + } + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + switch (field.getType()) { + case MESSAGE: + return field.getMessageType().hashCode(); + case ENUM: + return field.getEnumType().hashCode(); + default: + return field.getType().hashCode(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/model/Util.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/apache/wave/pst/model/Util.java b/pst/src/main/java/org/apache/wave/pst/model/Util.java new file mode 100644 index 0000000..373d3a7 --- /dev/null +++ b/pst/src/main/java/org/apache/wave/pst/model/Util.java @@ -0,0 +1,46 @@ +/** + * 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.model; + +/** + * Util methods for model objects. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class Util { + + private Util() { + } + + /** + * @return the given string, capitalized ("fooBar" = "FooBar") + */ + public static String capitalize(String s) { + return s.isEmpty() ? "" : Character.toUpperCase(s.charAt(0)) + s.substring(1); + } + + /** + * @return the given string, uncapitalized ("FooBar" = "fooBar") + */ + public static String uncapitalize(String s) { + return s.isEmpty() ? "" : Character.toLowerCase(s.charAt(0)) + s.substring(1); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/style/PstStyler.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/apache/wave/pst/style/PstStyler.java b/pst/src/main/java/org/apache/wave/pst/style/PstStyler.java new file mode 100644 index 0000000..ef8a437 --- /dev/null +++ b/pst/src/main/java/org/apache/wave/pst/style/PstStyler.java @@ -0,0 +1,513 @@ +/** + * 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.style; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.io.CharStreams; +import com.google.common.io.Files; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayDeque; +import java.util.Iterator; +import java.util.List; + +/** + * A code styler using a Builder approach to configure styles using smaller + * reformatting components. + * + * TODO(kalman): take string literals into account. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class PstStyler implements Styler { + + private static final String BACKUP_SUFFIX = ".prePstStyler"; + private static final String INDENT = " "; + private static final String[] ATOMIC_TOKENS = new String[] { + "} else {", + "} else if (", + "for (", + "/*-{", + "}-*/", + }; + + /** + * Builder for a series of composed style components. + */ + private static class StyleBuilder { + + /** + * Styles a single line, outputting generated lines to a generator. + * The styler may output anywhere between 0 and infinite lines. + */ + private interface LineStyler { + void next(String line, LineGenerator generator); + } + + /** + * Generates lines to some output sink. + */ + private interface LineGenerator { + void yield(CharSequence s); + } + + /** + * A pipeline of line stylers as a single line generator. + */ + private static final class LinePipeline implements LineGenerator { + private final LineStyler lineStyler; + private final LineGenerator next; + + private LinePipeline(LineStyler lineStyler, LineGenerator next) { + this.lineStyler = lineStyler; + this.next = next; + } + + /** + * Constructs a pipeline of line stylers. + * + * @param ls the line stylers to place in the pipeline + * @param sink the line generator at the end of the pipeline + * @return the head of the pipeline + */ + public static LinePipeline construct(Iterable<LineStyler> ls, LineGenerator sink) { + return new LinePipeline(head(ls), + (Iterables.size(ls) == 1) ? sink : construct(tail(ls), sink)); + } + + private static LineStyler head(Iterable<LineStyler> ls) { + return ls.iterator().next(); + } + + private static Iterable<LineStyler> tail(final Iterable<LineStyler> ls) { + return new Iterable<LineStyler>() { + @Override public Iterator<LineStyler> iterator() { + Iterator<LineStyler> tail = ls.iterator(); + tail.next(); + return tail; + } + }; + } + + @Override + public void yield(CharSequence s) { + lineStyler.next(s.toString(), next); + } + } + + /** + * Generates lines into a list. + */ + private static final class ListGenerator implements LineGenerator { + private final List<String> list = Lists.newArrayList(); + + @Override + public void yield(CharSequence s) { + list.add(s.toString()); + } + + public List<String> getList() { + return list; + } + } + + /** + * Maintains some helpful state across a styling. + */ + private abstract class StatefulLineStyler implements LineStyler { + private boolean inShortComment = false; + private boolean inLongComment = false; + private boolean inStartOfComment = false; + private boolean previousWasComment = false; + private int lineNumber = 0; + + protected boolean inComment() { + return inShortComment || inLongComment; + } + + protected boolean inStartOfComment() { + return inStartOfComment; + } + + protected boolean inLongComment() { + return inLongComment; + } + + protected int getLineNumber() { + return lineNumber; + } + + protected boolean previousWasComment() { + return previousWasComment; + } + + @Override + public final void next(String line, LineGenerator generator) { + lineNumber++; + // TODO(kalman): JSNI? + if (line.matches("^[ \t]*/\\*.*")) { + inLongComment = true; + inStartOfComment = true; + } + if (line.matches("^[ \t]*//.*")) { + inShortComment = true; + inStartOfComment = true; + } + doNext(line, generator); + previousWasComment = inShortComment || inLongComment; + if (line.contains("*/")) { + inLongComment = false; + } + inShortComment = false; + inStartOfComment = false; + } + + abstract void doNext(String line, LineGenerator generator); + } + + private final List<LineStyler> lineStylers = Lists.newArrayList(); + + /** + * Applies the state of the styler to a list of lines. + * + * @return the styled lines + */ + public List<String> apply(List<String> lines) { + ListGenerator result = new ListGenerator(); + LinePipeline pipeline = LinePipeline.construct(lineStylers, result); + for (String line : lines) { + pipeline.yield(line); + } + return result.getList(); + } + + public StyleBuilder addNewLineBefore(final char newLineBefore) { + lineStylers.add(new StatefulLineStyler() { + @Override public void doNext(String line, LineGenerator generator) { + // TODO(kalman): this is heavy-handed; be fine-grained and just don't + // split over tokens (need regexp, presumably). + if (inComment() || containsAtomicToken(line)) { + generator.yield(line); + return; + } + + StringBuilder s = new StringBuilder(); + for (char c : line.toCharArray()) { + if (c == newLineBefore) { + generator.yield(s); + s = new StringBuilder(); + } + s.append(c); + } + generator.yield(s); + } + }); + return this; + } + + public StyleBuilder addNewLineAfter(final char newLineAfter) { + lineStylers.add(new StatefulLineStyler() { + @Override public void doNext(String line, LineGenerator generator) { + // TODO(kalman): same as above. + if (inComment() || containsAtomicToken(line)) { + generator.yield(line); + return; + } + + StringBuilder s = new StringBuilder(); + for (char c : line.toCharArray()) { + s.append(c); + if (c == newLineAfter) { + generator.yield(s); + s = new StringBuilder(); + } + } + generator.yield(s); + } + }); + return this; + } + + public StyleBuilder trim() { + lineStylers.add(new LineStyler() { + @Override public void next(String line, LineGenerator generator) { + generator.yield(line.trim()); + } + }); + return this; + } + + public StyleBuilder removeRepeatedSpacing() { + lineStylers.add(new LineStyler() { + @Override public void next(String line, LineGenerator generator) { + generator.yield(line.replaceAll("[ \t]+", " ")); + } + }); + return this; + } + + public StyleBuilder stripBlankLines() { + lineStylers.add(new LineStyler() { + @Override public void next(String line, LineGenerator generator) { + if (!line.isEmpty()) { + generator.yield(line); + } + } + }); + return this; + } + + public StyleBuilder stripInitialBlankLine() { + lineStylers.add(new LineStyler() { + boolean firstLine = true; + @Override public void next(String line, LineGenerator generator) { + if (!firstLine || !line.isEmpty()) { + generator.yield(line); + } + firstLine = false; + } + }); + return this; + } + + public StyleBuilder stripDuplicateBlankLines() { + lineStylers.add(new LineStyler() { + boolean previousWasEmpty = false; + @Override public void next(String line, LineGenerator generator) { + if (!previousWasEmpty || !line.isEmpty()) { + generator.yield(line); + } + previousWasEmpty = line.isEmpty(); + } + }); + return this; + } + + public StyleBuilder indentBraces() { + lineStylers.add(new StatefulLineStyler() { + private int indentLevel = 0; + + @Override public void doNext(String line, LineGenerator generator) { + if (!ignore(line) && line.contains("}")) { + indentLevel--; + Preconditions.checkState(indentLevel >= 0, + "Indentation level reached < 0 on line " + getLineNumber() + " (" + line + ")"); + } + String result = ""; + if (!line.isEmpty()) { + result = Strings.repeat(INDENT, indentLevel) + line; + } + if (!ignore(line) && line.contains("{")) { + indentLevel++; + } + generator.yield(result.toString()); + } + + private boolean ignore(String line) { + // Ignore self-closing braces. + return line.contains("{") + && line.contains("}") + && line.indexOf('{') < line.lastIndexOf('}'); + } + }); + return this; + } + + public StyleBuilder indentLongComments() { + lineStylers.add(new StatefulLineStyler() { + @Override void doNext(String line, LineGenerator generator) { + if (inLongComment() && !inStartOfComment()) { + generator.yield(" " + line); + } else { + generator.yield(line); + } + } + }); + return this; + } + + public StyleBuilder doubleIndentUnfinishedLines() { + lineStylers.add(new StatefulLineStyler() { + boolean previousUnfinished = false; + + @Override public void doNext(String line, LineGenerator generator) { + generator.yield((previousUnfinished ? Strings.repeat(INDENT, 2) : "") + line); + previousUnfinished = + !inComment() && + !line.matches("^.*[;{},\\-/]$") && // Ends with an expected character. + !line.contains("@Override") && // or an annotation. + !line.isEmpty() && + !line.contains("//"); // Single-line comment. + } + }); + return this; + } + + public StyleBuilder addBlankLineBeforeMatching(final String regex) { + lineStylers.add(new StatefulLineStyler() { + @Override public void doNext(String line, LineGenerator generator) { + if ((!inComment() || inStartOfComment()) && line.matches(regex)) { + generator.yield(""); + } + generator.yield(line); + } + }); + return this; + } + + public StyleBuilder addBlankLineBeforeClasslikeWithNoPrecedingComment() { + lineStylers.add(new StatefulLineStyler() { + @Override public void doNext(String line, LineGenerator generator) { + if (!previousWasComment() + && line.matches(".*\\b(class|interface|enum)\\b.*")) { + generator.yield(""); + } + generator.yield(line); + } + }); + return this; + } + + public StyleBuilder addBlankLineAfterBraceUnlessInMethod() { + lineStylers.add(new StatefulLineStyler() { + // true for every level of braces that is a class-like construct + ArrayDeque<Boolean> stack = new ArrayDeque<Boolean>(); + boolean sawClasslike = false; + + @Override public void doNext(String line, LineGenerator generator) { + if (inComment()) { + generator.yield(line); + } else if (line.endsWith("}") && !line.contains("{")) { + generator.yield(line); + stack.pop(); + if (!stack.isEmpty() && stack.peek()) { + generator.yield(""); + } + } else { + // Perhaps we could match anonymous classes by adding "new" here, + // but this is not currently needed. + if (line.matches(".*\\b(class|interface|enum)\\b.*")) { + sawClasslike = true; + } + if (line.endsWith("{")) { + if (line.contains("}")) { + stack.pop(); + } + stack.push(sawClasslike); + sawClasslike = false; + } else if (line.endsWith(";")) { + sawClasslike = false; + } + generator.yield(line); + } + } + }); + return this; + } + + public StyleBuilder addBlankLineAfterMatching(final String regex) { + lineStylers.add(new StatefulLineStyler() { + boolean previousLineMatched = false; + + @Override public void doNext(String line, LineGenerator generator) { + if (previousLineMatched) { + generator.yield(""); + } + generator.yield(line); + previousLineMatched = line.matches(regex); + } + }); + return this; + } + + private boolean containsAtomicToken(String line) { + for (String token : ATOMIC_TOKENS) { + if (line.contains(token)) { + return true; + } + } + return false; + } + } + + @Override + public void style(File f, boolean saveBackup) { + List<String> lines = null; + try { + lines = CharStreams.readLines(new FileReader(f)); + } catch (IOException e) { + System.err.println("Couldn't find file " + f.getName() + " to style: " + e.getMessage()); + return; + } + + Joiner newlineJoiner = Joiner.on('\n'); + + if (saveBackup) { + File backup = new File(f.getAbsolutePath() + BACKUP_SUFFIX); + try { + Files.write(newlineJoiner.join(lines), backup, Charset.defaultCharset()); + } catch (IOException e) { + System.err.println("Couldn't write backup " + backup.getName() + ": " + e.getMessage()); + return; + } + } + + try { + Files.write(newlineJoiner.join(styleLines(lines)), f, Charset.defaultCharset()); + } catch (IOException e) { + System.err.println("Couldn't write styled file " + f.getName() + ": " + e.getMessage()); + return; + } + } + + private List<String> styleLines(List<String> lines) { + return new StyleBuilder() + .trim() + .removeRepeatedSpacing() + .addNewLineBefore('}') + .addNewLineAfter('{') + .addNewLineAfter('}') + .addNewLineAfter(';') + .trim() + .removeRepeatedSpacing() + .stripBlankLines() + .trim() + .indentBraces() + .indentLongComments() + .addBlankLineBeforeMatching("[ \t]*@Override.*") + .addBlankLineBeforeMatching(".*/\\*\\*.*") + .addBlankLineAfterMatching("package.*") + .addBlankLineBeforeMatching("package.*") + .addBlankLineBeforeClasslikeWithNoPrecedingComment() + .addBlankLineAfterBraceUnlessInMethod() + .stripDuplicateBlankLines() + .doubleIndentUnfinishedLines() + .stripInitialBlankLine() + // TODO: blank line before first method or constructor if that has no javadoc + .apply(lines); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/style/Styler.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/apache/wave/pst/style/Styler.java b/pst/src/main/java/org/apache/wave/pst/style/Styler.java new file mode 100644 index 0000000..3621411 --- /dev/null +++ b/pst/src/main/java/org/apache/wave/pst/style/Styler.java @@ -0,0 +1,45 @@ +/** + * 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.style; + +import java.io.File; + +/** + * Styles a source file. + * + * @author [email protected] (Benjamin Kalman) + */ +public interface Styler { + + /** + * Styles a source file. + * + * @param f the file to style + * @param saveBackup whether to save a backup + */ + void style(File f, boolean saveBackup); + + /** + * No-op implementation. + */ + Styler EMPTY = new Styler() { + @Override public void style(File f, boolean saveBackup) {} + }; +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/apache/wave/pst/testing/RandomProtobufGenerator.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/apache/wave/pst/testing/RandomProtobufGenerator.java b/pst/src/main/java/org/apache/wave/pst/testing/RandomProtobufGenerator.java new file mode 100644 index 0000000..369e21a --- /dev/null +++ b/pst/src/main/java/org/apache/wave/pst/testing/RandomProtobufGenerator.java @@ -0,0 +1,170 @@ +/** + * 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.testing; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.protobuf.ByteString; +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.EnumDescriptor; +import com.google.protobuf.Descriptors.EnumValueDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.GeneratedMessage; +import com.google.protobuf.Message.Builder; + +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * Generates random protocol buffers with all fields given a value. + * + * @author [email protected] (Benjamin Kalman) + */ +public final class RandomProtobufGenerator<E extends GeneratedMessage> { + + private static final int MAX_LIST_LENGTH = 10; + private static final int MAX_STRING_LENGTH = 10; + + private final Random random; + private final GeneratedMessage instance; + private final Map<Descriptor, GeneratedMessage> moreInstances; + + private RandomProtobufGenerator(Random random, GeneratedMessage instance, + Map<Descriptor, GeneratedMessage> moreInstances) { + this.random = random; + this.instance = instance; + this.moreInstances = moreInstances; + } + + /** + * Creates a random protobuf generator, for a main type (instance) and a list + * of any extra instances that will be needed to generate that instance. + * + * @param random random number generator + * @param instance the protobuf instance used as a template to generate more + * random protobuf + * @param moreInstances protobuf templates for any inner messages the + * protobuf depends on + * @return the random protobuf generator + */ + public static <T extends GeneratedMessage> RandomProtobufGenerator<T> create(Random random, + GeneratedMessage instance, GeneratedMessage... moreInstances) { + // NOTE: it would be nice to determine this internally e.g. through (java or proto) reflection. + Map<Descriptor, GeneratedMessage> moreInstancesMap = Maps.newHashMap(); + for (GeneratedMessage gm : moreInstances) { + moreInstancesMap.put(gm.getDescriptorForType(), gm); + } + return new RandomProtobufGenerator<T>(random, instance, moreInstancesMap); + } + + /** + * Generates a random protocol buffer, filling in all fields and giving + * repeated values 0..n items. + */ + public E generate() { + return generate(0); + } + + /** + * Generates a random protocol buffer, filling in all required fields but + * with a p chance of not setting an optional field and p chance of having + * an empty repeated field. + */ + @SuppressWarnings("unchecked") + public E generate(double p) { + Builder builder = instance.newBuilderForType(); + Descriptor descriptor = instance.getDescriptorForType(); + for (FieldDescriptor field : descriptor.getFields()) { + if (!field.isRequired() && random.nextDouble() < p) { + continue; + } + builder.setField(field, getRandomValue(field, p)); + } + return (E) builder.build(); + } + + private Object getRandomValue(FieldDescriptor field, double p) { + if (field.isRepeated()) { + List<Object> values = Lists.newArrayList(); + for (int i = 0, length = random.nextInt(MAX_LIST_LENGTH); i < length; i++) { + values.add(getRandomSingleValue(field, p)); + } + return values; + } else { + return getRandomSingleValue(field, p); + } + } + + private Object getRandomSingleValue(FieldDescriptor field, double p) { + switch (field.getJavaType()) { + case BOOLEAN: + return random.nextBoolean(); + case BYTE_STRING: + return getRandomByteString(); + case DOUBLE: + return random.nextDouble(); + case ENUM: + return getRandomEnum(field.getEnumType()); + case FLOAT: + return random.nextFloat(); + case INT: + return random.nextInt(); + case LONG: + return random.nextLong(); + case MESSAGE: + return getRandomMessage(field, p); + case STRING: + return getRandomString(); + default: + return null; + } + } + + private ByteString getRandomByteString() { + byte[] bytes = new byte[32]; + random.nextBytes(bytes); + return ByteString.copyFrom(bytes); + } + + private EnumValueDescriptor getRandomEnum(EnumDescriptor enumD) { + List<EnumValueDescriptor> values = enumD.getValues(); + return values.get(random.nextInt(values.size())); + } + + private GeneratedMessage getRandomMessage(FieldDescriptor field, double p) { + GeneratedMessage instance = moreInstances.get(field.getMessageType()); + if (instance == null) { + throw new IllegalArgumentException("Couldn't find instance for message " + + field.getMessageType().getFullName()); + } + return new RandomProtobufGenerator<GeneratedMessage>(random, instance, moreInstances) + .generate(p); + } + + private String getRandomString() { + String alphabet = "abc{}[]<>\\\"'"; + StringBuilder s = new StringBuilder(); + for (int i = 0, length = random.nextInt(MAX_STRING_LENGTH); i < length; i++) { + s.append(alphabet.charAt(random.nextInt(alphabet.length()))); + } + return s.toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/waveprotocol/pst/Pst.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/Pst.java b/pst/src/main/java/org/waveprotocol/pst/Pst.java deleted file mode 100644 index 98289e3..0000000 --- a/pst/src/main/java/org/waveprotocol/pst/Pst.java +++ /dev/null @@ -1,188 +0,0 @@ -/** - * 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.waveprotocol.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.waveprotocol.pst.model.Message; -import org.waveprotocol.pst.model.MessageProperties; -import org.waveprotocol.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 [email protected] (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; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/waveprotocol/pst/PstCommandLine.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/PstCommandLine.java b/pst/src/main/java/org/waveprotocol/pst/PstCommandLine.java deleted file mode 100644 index dbc0223..0000000 --- a/pst/src/main/java/org/waveprotocol/pst/PstCommandLine.java +++ /dev/null @@ -1,150 +0,0 @@ -/** - * 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.waveprotocol.pst; - -import com.google.common.base.Function; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; - -import org.apache.commons.cli.BasicParser; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; -import org.waveprotocol.pst.style.PstStyler; -import org.waveprotocol.pst.style.Styler; - -import java.io.File; -import java.util.Map; - -/** - * Encapsulates the command line options to protobuf-stringtemplate. - * - * @author [email protected] (Benjamin Kalman) - */ -public final class PstCommandLine { - - private static final String DEFAULT_OUTPUT_DIR = "."; - private static final String DEFAULT_PROTO_PATH = "."; - private static final Map<String, Styler> STYLERS = ImmutableMap.<String, Styler> builder() - .put("none", Styler.EMPTY) - .put("pst", new PstStyler()) - .build(); - - private final CommandLine cl; - - public PstCommandLine(String... args) throws ParseException { - cl = new BasicParser().parse(getOptions(), args); - checkArgs(); - } - - private void checkArgs() throws ParseException { - if (!hasFile()) { - throw new ParseException("Must specify file"); - } - if (cl.getArgList().isEmpty()) { - throw new ParseException("Must specify at least one template"); - } - } - - private static Options getOptions() { - Options options = new Options(); - options.addOption("h", "help", false, "Show this help"); - options.addOption("f", "file", true, "The protobuf specification file to use"); - options.addOption("d", "dir", true, String.format( - "The base directory to output generated files to (default: %s)", DEFAULT_OUTPUT_DIR)); - options.addOption("s", "styler", true, "The styler to use, if any (default: none). " + - "Available options: " + STYLERS.keySet()); - options.addOption("i", "save_pre_styled", false, "Save the intermediate pre-styled files"); - options.addOption("j", "save_java", false, "Save the protoc-generated Java file, if any"); - options.addOption("I", "proto_path", true, "Extra path to search for proto extensions. " - + "This needs to be specified if the target file is a .proto file with any of the PST-" - + "specific extensions, in which case the path should include both PST source " - + "base and the protoc source base; i.e., /PATH/TO/PST/src:/PATH/TO/PROTOC/src"); - options.addOption("t", "int52", true, - "Specifies if pst should store 64-bit integers should be serialized to" - + "doubles which will use 52-bit precision. It's useful " - + "when data is meant to be serialized/deserialized in JavaScript, since it doesn't " - + "support 64-bit integers (default: false)."); - return options; - } - - public boolean hasHelp() { - return cl.hasOption('h'); - } - - // NOTE: private because it's always true, checked in checkArgs(). - private boolean hasFile() { - return cl.hasOption('f'); - } - - public static void printHelp() { - new HelpFormatter().printHelp( - PstMain.class.getSimpleName() + " [options] templates...", getOptions()); - } - - public File getProtoFile() { - return new File(cl.getOptionValue('f')); - } - - @SuppressWarnings("unchecked") - public Iterable<File> getTemplateFiles() { - return Iterables.transform(cl.getArgList(), new Function<String, File>() { - @Override public File apply(String filename) { - return new File(filename); - } - }); - } - - public File getOutputDir() { - return new File(cl.hasOption('d') ? cl.getOptionValue('d') : DEFAULT_OUTPUT_DIR); - } - - public File getProtoPath() { - return new File(cl.hasOption('I') ? cl.getOptionValue('I') : DEFAULT_PROTO_PATH); - } - - public Styler getStyler() { - if (cl.hasOption('s')) { - String stylerName = cl.getOptionValue('s'); - if (STYLERS.containsKey(stylerName)) { - return STYLERS.get(stylerName); - } else { - System.err.println("WARNING: unrecognised styler: " + stylerName + ", using none"); - return Styler.EMPTY; - } - } else { - return Styler.EMPTY; - } - } - - public boolean shouldSavePreStyled() { - return cl.hasOption('p'); - } - - public boolean shouldSaveJava() { - return cl.hasOption('j'); - } - - public boolean shouldUseInt52() { - return !cl.hasOption('t') // - || (cl.hasOption('t') && "true".equals(cl.getOptionValue('t'))); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/36445930/pst/src/main/java/org/waveprotocol/pst/PstException.java ---------------------------------------------------------------------- diff --git a/pst/src/main/java/org/waveprotocol/pst/PstException.java b/pst/src/main/java/org/waveprotocol/pst/PstException.java deleted file mode 100644 index 388bd77..0000000 --- a/pst/src/main/java/org/waveprotocol/pst/PstException.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * 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.waveprotocol.pst; - -import com.google.common.collect.ImmutableList; - -import java.util.List; - -/** - * Exception caused by any errors caused in code generation. - * - * @author [email protected] (Benjamin Kalman) - */ -public final class PstException extends Exception { - - public static final class TemplateException extends Exception { - private final String templateName; - - public TemplateException(String templateName, String message, Throwable cause) { - super(message, cause); - this.templateName = templateName; - } - - public TemplateException(String templateName, Throwable cause) { - super(cause); - this.templateName = templateName; - } - - /** - * @return the name of the template being parsed when the exception occurred - */ - public String getTemplateName() { - return templateName; - } - } - - private final ImmutableList<TemplateException> exceptions; - - public PstException(List<TemplateException> exceptions) { - super(); - this.exceptions = ImmutableList.copyOf(exceptions); - } - - /** - * @return all exceptions caused - */ - public ImmutableList<TemplateException> getTemplateExceptions() { - return exceptions; - } -}
