blob: e4353c5f75b740c238d7c02e0aca7f85c1898fdb [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed 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 com.google.gwt.uibinder.rebind.messages;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.uibinder.rebind.IndentedWriter;
import com.google.gwt.uibinder.rebind.MortalLogger;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLAttribute;
import com.google.gwt.uibinder.rebind.XMLElement;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* TemplateWriter for messages.
*/
public class MessagesWriter {
public static final String ATTRIBUTE = "attribute";
private static final String NAME = "name";
private static final String[] EMPTY_ARRAY = {};
private final String messagesNamespaceURI;
private final String packageName;
private final String messagesClassName;
private final MortalLogger logger;
private final List<MessageWriter> messages = new ArrayList<MessageWriter>();
private final String generatedFrom;
private String defaultLocale;
private String messagesPrefix;
private String generateKeys;
private GenerateAnnotationWriter generate;
private Map<XMLElement, Collection<AttributeMessage>> elemToAttributeMessages =
new HashMap<XMLElement, Collection<AttributeMessage>>();
public MessagesWriter(String nameSpaceUri, MortalLogger mortalLogger, String generatedFrom,
String packageName, String uiBinderImplClassName) {
this.messagesNamespaceURI = nameSpaceUri;
this.generatedFrom = generatedFrom;
this.packageName = packageName;
// Localizable classes cannot have underscores in their names.
this.messagesClassName = uiBinderImplClassName.replaceAll("_", "") + "GenMessages";
this.logger = mortalLogger;
}
/**
* Call {@link #consumeAttributeMessages}, but instead of returning the
* results store them for retrieval by a later call to
* {@link #retrieveMessageAttributesFor}.
*
* @throws UnableToCompleteException on error
*/
public void consumeAndStoreMessageAttributesFor(XMLElement elem)
throws UnableToCompleteException {
Collection<AttributeMessage> attributeMessages =
consumeAttributeMessages(elem);
if (!attributeMessages.isEmpty()) {
elemToAttributeMessages.put(elem, attributeMessages);
}
}
/**
* Examine the children of the given element. Consume those tagged m:attribute
* and return a set of {@link AttributeMessage} instances. E.g.:
* <p>
*
* <pre>
* &lt;img src="blueSky.jpg" alt="A blue sky">
* &lt;ui:attribute name="alt" description="blue sky image alt text"/>
* &lt;/img>
* </pre>
*
* <p>
*/
public Collection<AttributeMessage> consumeAttributeMessages(XMLElement elem)
throws UnableToCompleteException {
Collection<XMLElement> messageChildren = getAttributeMessageChildren(elem);
if (messageChildren.isEmpty()) {
return Collections.emptySet();
}
Set<AttributeMessage> attributeMessages = new HashSet<AttributeMessage>();
for (XMLElement child : messageChildren) {
String attributeName = consumeMessageElementAttribute(NAME, child);
if (attributeName.length() == 0) {
logger.die(child, "Missing name attribute");
}
if (!elem.hasAttribute(attributeName)) {
logger.die(child, "Enclosing element has no matching attribute");
}
XMLAttribute attribute = elem.getAttribute(attributeName);
if (attribute.hasComputedValue()) {
logger.die(elem, "Attribute \"%s\" has a field reference and "
+ "so cannot be marked for localization, but found %s",
attributeName, child);
}
String defaultMessage = MessageWriter.escapeMessageFormat(elem.consumeRawAttribute(attributeName));
defaultMessage = UiBinderWriter.escapeTextForJavaStringLiteral(defaultMessage);
attributeMessages.add(new AttributeMessage(attributeName, declareMessage(
child, defaultMessage)));
}
return attributeMessages;
}
/**
* Consume an m:blah attribute on a non-message element, e.g.
* {@code <span m:ph="fnord"/>}
*/
public String consumeMessageAttribute(String attName, XMLElement elem) {
String fullAttName = getMessagesPrefix() + ":" + attName;
return elem.consumeRawAttribute(fullAttName, "");
}
/**
* Declares a message created by a previous call to {@link #newMessage}, and
* returns its invocation expression to be stitched into an innerHTML block.
*/
public String declareMessage(MessageWriter newMessage) {
messages.add(newMessage);
return String.format("messages.%s", newMessage.getInvocation());
}
/**
* Expected to be called with the root element, to allow configuration from
* various messages related attributes.
*/
public void findMessagesConfig(XMLElement elem) {
String prefix = elem.lookupPrefix(getMessagesUri());
if (prefix != null) {
messagesPrefix = prefix;
defaultLocale = consumeMessageAttribute("defaultLocale", elem);
generateKeys = consumeMessageAttribute("generateKeys", elem);
generate =
new GenerateAnnotationWriter(getMessageAttributeStringArray(
"generateFormat", elem), consumeMessageAttribute(
"generateFilename", elem), getMessageAttributeStringArray(
"generateLocales", elem));
}
}
/**
* @return the expression that will instantiate the Messages interface
*/
public String getDeclaration() {
return String.format(
"static %1$s messages = (%1$s) GWT.create(%1$s.class);",
getMessagesClassName());
}
public String getMessagesClassName() {
return messagesClassName;
}
/**
* @return The namespace prefix (not including :) declared by the template for
* message elements and attributes
*/
public String getMessagesPrefix() {
return messagesPrefix;
}
/**
* Confirm existence of an m:blah attribute on a non-message element, e.g.
* {@code <span ui:ph="fnord"/>}
*/
public boolean hasMessageAttribute(String attName, XMLElement elem) {
String fullAttName = getMessagesPrefix() + ":" + attName;
return elem.hasAttribute(fullAttName);
}
/**
* @return true iff any messages have been declared
*/
public boolean hasMessages() {
return !messages.isEmpty();
}
public boolean isMessage(XMLElement elem) {
return isMessagePrefixed(elem) && "msg".equals(elem.getLocalName());
}
/**
* Creates a new MessageWriter instance with description, key and meaning
* values consumed from the given XMLElement. Note that this message will not
* be written in the generated code unless it is later declared via
* {@link #declareMessage(MessageWriter)}
*/
public MessageWriter newMessage(XMLElement elem) {
MessageWriter newMessage =
new MessageWriter(consumeMessageElementAttribute("description", elem),
consumeMessageElementAttribute("key", elem),
consumeMessageElementAttribute("meaning", elem),
nextMessageName());
return newMessage;
}
/**
* @return The set of AttributeMessages that were found in elem and stored by
* a previous call to {@link #consumeAndStoreMessageAttributesFor}
*/
public Collection<AttributeMessage> retrieveMessageAttributesFor(
XMLElement elem) {
return elemToAttributeMessages.get(elem);
}
public void write(PrintWriter printWriter) {
IndentedWriter writer = new IndentedWriter(printWriter);
// Package declaration.
if (packageName.length() > 0) {
writer.write("package %1$s;", packageName);
writer.newline();
}
// Imports.
writer.write("import com.google.gwt.i18n.client.Messages;");
writer.write("import static com.google.gwt.i18n.client.LocalizableResource.*;");
writer.newline();
// Open interface.
genInterfaceAnnotations(writer);
writer.write("public interface %s extends Messages {", getMessagesClassName());
writer.newline();
writer.indent();
// Write message methods
for (MessageWriter m : messages) {
m.writeDeclaration(writer);
}
// Close interface.
writer.outdent();
writer.write("}");
}
/**
* Consume an attribute on a messages related element (as oppposed to a
* messages attribute in some other kind of element), e.g. the description in
* {@code <m:msg description="described!">}
*/
String consumeMessageElementAttribute(String attName, XMLElement elem) {
if (elem.hasAttribute(attName)) {
return UiBinderWriter.escapeTextForJavaStringLiteral(elem.consumeRawAttribute(attName));
}
String fullAttName = getMessagesPrefix() + ":" + attName;
if (elem.hasAttribute(fullAttName)) {
String value = elem.consumeRawAttribute(fullAttName);
logger.warn(elem,
"Deprecated prefix \"%s:\" on \"%s\". Use \"%s\" instead.",
getMessagesPrefix(), fullAttName, attName);
return value;
}
return "";
}
String consumeRequiredMessageElementAttribute(String attName,
XMLElement elem) throws UnableToCompleteException {
String value = consumeMessageElementAttribute(attName, elem);
if ("".equals(value)) {
logger.die(elem, "Missing required attribute %s", attName);
}
return value;
}
boolean isMessagePrefixed(XMLElement elem) {
String uri = elem.getNamespaceUri();
return uri != null && uri.startsWith(getMessagesUri());
}
private String declareMessage(XMLElement elem, String defaultMessage) {
List<PlaceholderWriter> emptyList = Collections.emptyList();
MessageWriter newMessage = newMessage(elem);
newMessage.setDefaultMessage(defaultMessage);
for (PlaceholderWriter placeholder : emptyList) {
newMessage.addPlaceholder(placeholder);
}
return declareMessage(newMessage);
}
private void genInterfaceAnnotations(IndentedWriter pw) {
pw.write("@GeneratedFrom(\"%s\")", generatedFrom);
if (defaultLocale.length() > 0) {
pw.write("@DefaultLocale(\"%s\")", defaultLocale);
}
if (generateKeys.length() > 0) {
pw.write("@GenerateKeys(\"%s\")", generateKeys);
}
generate.write(pw);
}
private Collection<XMLElement> getAttributeMessageChildren(
final XMLElement elem) throws UnableToCompleteException {
return elem.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
public Boolean interpretElement(XMLElement child)
throws UnableToCompleteException {
if (isAttributeMessage(child)) {
if (child.hasChildNodes()) {
logger.die(child, "Illegal body.", child, elem);
}
return true;
}
return false;
}
});
}
private String[] getMessageAttributeStringArray(String attName,
XMLElement elem) {
String value = consumeMessageAttribute(attName, elem);
if (value == null) {
return EMPTY_ARRAY;
}
return value.split("\\s*,\\s*");
}
private String getMessagesUri() {
return messagesNamespaceURI;
}
private boolean isAttributeMessage(XMLElement elem) {
return isMessagePrefixed(elem) && ATTRIBUTE.equals(elem.getLocalName());
}
private String nextMessageName() {
return String.format("message%d", messages.size() + 1);
}
}