| /* |
| * Copyright 2011 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.i18n.server; |
| |
| import com.google.gwt.i18n.client.Constants.DefaultStringMapValue; |
| import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale; |
| import com.google.gwt.i18n.client.LocalizableResource.Description; |
| import com.google.gwt.i18n.client.LocalizableResource.GenerateKeys; |
| import com.google.gwt.i18n.client.LocalizableResource.Key; |
| import com.google.gwt.i18n.client.LocalizableResource.Meaning; |
| import com.google.gwt.i18n.client.Messages.AlternateMessage; |
| import com.google.gwt.i18n.client.Messages.DefaultMessage; |
| import com.google.gwt.i18n.client.Messages.PluralCount; |
| import com.google.gwt.i18n.client.Messages.Select; |
| import com.google.gwt.i18n.server.MessageFormatUtils.MessageStyle; |
| import com.google.gwt.i18n.server.MessageUtils.KeyGeneratorException; |
| import com.google.gwt.i18n.shared.AlternateMessageSelector; |
| import com.google.gwt.i18n.shared.AlternateMessageSelector.AlternateForm; |
| import com.google.gwt.i18n.shared.GwtLocale; |
| import com.google.gwt.i18n.shared.GwtLocaleFactory; |
| |
| import java.lang.annotation.Annotation; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Base implementation of {@link Message}. |
| */ |
| public abstract class AbstractMessage implements Message { |
| |
| private List<AlternateForm> defaultForms; |
| |
| private String defaultMessage; |
| |
| private boolean isStringMap; |
| |
| private String key = null; |
| |
| private final GwtLocaleFactory localeFactory; |
| |
| private GwtLocale matchedLocale; |
| |
| private String meaning; |
| |
| private final MessageInterface msgIntf; |
| |
| private MessageStyle messageStyle; |
| |
| private AlternateMessageSelector[] selectors; |
| |
| private int[] selectorParams; |
| |
| private MessageTranslation overrideDefault; |
| |
| public AbstractMessage(GwtLocaleFactory localeFactory, |
| MessageInterface msgIntf) { |
| this.localeFactory = localeFactory; |
| this.msgIntf = msgIntf; |
| } |
| |
| public void accept(MessageVisitor mv) throws MessageProcessingException { |
| accept(mv, null); |
| } |
| |
| public void accept(MessageVisitor mv, GwtLocale locale) |
| throws MessageProcessingException { |
| |
| ensureSelectorParams(); |
| List<Parameter> params = getParameters(); |
| int numSelectors = selectorParams.length; |
| String[] lastForm = new String[numSelectors]; |
| |
| // lookup the translation to use |
| MessageTranslation trans = null; |
| if (locale != null) { |
| for (GwtLocale search : locale.getCompleteSearchList()) { |
| trans = getTranslation(search); |
| if (trans != null) { |
| break; |
| } |
| } |
| } |
| if (trans == null) { |
| trans = this; |
| } |
| |
| for (AlternateFormMapping mapping : trans.getAllMessageForms()) { |
| List<AlternateForm> forms = mapping.getForms(); |
| boolean allOther = true; |
| for (int i = 0; i < forms.size(); ++i) { |
| lastForm[i] = forms.get(i).getName(); |
| if (!AlternateMessageSelector.OTHER_FORM_NAME.equals(lastForm[i])) { |
| allOther = false; |
| } |
| } |
| mv.visitTranslation(lastForm, allOther, messageStyle, |
| mapping.getMessage()); |
| } |
| |
| mv.endMessage(this, trans); |
| } |
| |
| public int compareTo(Message o) { |
| return getKey().compareTo(o.getKey()); |
| } |
| |
| public Iterable<AlternateFormMapping> getAllMessageForms() { |
| if (overrideDefault != null) { |
| return overrideDefault.getAllMessageForms(); |
| } |
| List<AlternateFormMapping> mapping = new ArrayList<AlternateFormMapping>(); |
| List<Parameter> params = getParameters(); |
| int[] selectorIndices = getSelectorParameterIndices(); |
| int numSelectors = selectorIndices.length; |
| |
| // add the default form |
| if (!isStringMap) { |
| mapping.add(new AlternateFormMapping(defaultForms, getDefaultMessage())); |
| } |
| |
| // look for alternate forms |
| String[] altMessages = null; |
| if (isStringMap) { |
| DefaultStringMapValue smv = getAnnotation(DefaultStringMapValue.class); |
| if (smv != null) { |
| altMessages = smv.value(); |
| } |
| } else { |
| altMessages = getAlternateMessages(); |
| } |
| if (altMessages == null) { |
| return mapping; |
| } |
| int n = altMessages.length; |
| // TODO(jat): check for even number? |
| for (int msgIdx = 0; msgIdx < n; msgIdx += 2) { |
| addMapping(mapping, numSelectors, altMessages[msgIdx], |
| altMessages[msgIdx + 1]); |
| } |
| |
| // sort into lexicographic order and return |
| Collections.sort(mapping); |
| return mapping; |
| } |
| |
| public abstract <A extends Annotation> A getAnnotation(Class<A> annotClass); |
| |
| public String getDefaultMessage() { |
| if (overrideDefault != null) { |
| return overrideDefault.getDefaultMessage(); |
| } |
| return defaultMessage; |
| } |
| |
| public String getDescription() { |
| Description descAnnot = getAnnotation(Description.class); |
| if (descAnnot != null) { |
| return descAnnot.value(); |
| } |
| return null; |
| } |
| |
| public String getKey() { |
| KeyGeneratorException keyGenException = null; |
| if (key == null) { |
| Key keyAnnot = getAnnotation(Key.class); |
| if (keyAnnot != null) { |
| key = keyAnnot.value(); |
| } else { |
| GenerateKeys keyGenAnnot = getAnnotation(GenerateKeys.class); |
| try { |
| KeyGenerator keyGen = MessageUtils.getKeyGenerator(keyGenAnnot); |
| key = keyGen.generateKey(this); |
| } catch (KeyGeneratorException e) { |
| keyGenException = e; |
| } |
| } |
| } |
| if (key == null) { |
| GenerateKeys keyGenAnnot = getAnnotation(GenerateKeys.class); |
| // If we were unable to get a key, things will fail later. Instead, fail |
| // here where the backtrace has useful information about the cause. |
| throw new RuntimeException("null key on " |
| + getMessageInterface().getQualifiedName() + "." + getMethodName() |
| + ", @GenerateKeys=" + keyGenAnnot + ", defmsg=" + defaultMessage |
| + ", meaning=" + meaning + ", @DefaultMessage=" |
| + getAnnotation(DefaultMessage.class) + ", @Meaning=" |
| + getAnnotation(Meaning.class) + ", override=" + overrideDefault, |
| keyGenException); |
| } |
| return key; |
| } |
| |
| public GwtLocale getMatchedLocale() { |
| if (overrideDefault != null) { |
| return overrideDefault.getMatchedLocale(); |
| } |
| return matchedLocale; |
| } |
| |
| public String getMeaning() { |
| return meaning; |
| } |
| |
| public MessageInterface getMessageInterface() { |
| return msgIntf; |
| } |
| |
| public MessageStyle getMessageStyle() { |
| return messageStyle; |
| } |
| |
| public abstract String getMethodName(); |
| |
| public abstract List<Parameter> getParameters(); |
| |
| public abstract Type getReturnType(); |
| |
| public int[] getSelectorParameterIndices() { |
| if (selectorParams == null) { |
| ensureSelectorParams(); |
| } |
| return selectorParams; |
| } |
| |
| public abstract MessageTranslation getTranslation(GwtLocale locale); |
| |
| public abstract boolean isAnnotationPresent( |
| Class<? extends Annotation> annotClass); |
| |
| protected void addMapping(List<AlternateFormMapping> mapping, |
| int numSelectors, String joinedForms, String msg) { |
| String[] formNames = joinedForms.split("\\|"); |
| if (formNames.length != numSelectors) { |
| // TODO(jat): warn about invalid number of forms |
| return; |
| } |
| List<AlternateForm> forms = new ArrayList<AlternateForm>(); |
| boolean nonOther = false; |
| for (int selIdx = 0; selIdx < numSelectors; ++selIdx) { |
| String formName = formNames[selIdx]; |
| if (!selectors[selIdx].isFormAcceptable(formName)) { |
| // TODO(jat): warn about invalid form |
| nonOther = false; |
| break; |
| } |
| if (isStringMap || !AlternateMessageSelector.OTHER_FORM_NAME.equals(formName)) { |
| nonOther = true; |
| } |
| forms.add(new AlternateForm(formName, formName)); |
| } |
| if (!nonOther) { |
| // TODO(jat): warn about all others in alternate form |
| } else { |
| mapping.add(new AlternateFormMapping(forms, msg)); |
| } |
| } |
| |
| protected List<AlternateForm> defaultForms() { |
| return defaultForms; |
| } |
| |
| /** |
| * Get the alternate message forms from either an AlternateMessages annotation |
| * or a PluralText annotation. |
| */ |
| @SuppressWarnings("deprecation") |
| protected String[] getAlternateMessages() { |
| AlternateMessage altMsgAnnot = getAnnotation(AlternateMessage.class); |
| if (altMsgAnnot != null) { |
| return altMsgAnnot.value(); |
| } |
| // avoid deprecation warning for the import |
| com.google.gwt.i18n.client.Messages.PluralText pluralTextAnnot |
| = getAnnotation(com.google.gwt.i18n.client.Messages.PluralText.class); |
| if (pluralTextAnnot != null) { |
| return pluralTextAnnot.value(); |
| } |
| return null; |
| } |
| |
| protected GwtLocale getDefaultLocale() { |
| DefaultLocale defLocaleAnnot = getAnnotation(DefaultLocale.class); |
| String defLocale = defLocaleAnnot != null ? defLocaleAnnot.value() |
| : DefaultLocale.DEFAULT_LOCALE; |
| return localeFactory.fromString(defLocale); |
| } |
| |
| protected GwtLocaleFactory getLocaleFactory() { |
| return localeFactory; |
| } |
| |
| /** |
| * Called by subclasses to complete initialization, after ensuring that calls |
| * to {@link #getAnnotation(Class)} will function properly. |
| */ |
| protected void init() { |
| matchedLocale = getDefaultLocale(); |
| if (isAnnotationPresent(DefaultMessage.class)) { |
| messageStyle = MessageStyle.MESSAGE_FORMAT; |
| DefaultMessage defMsgAnnot = getAnnotation(DefaultMessage.class); |
| defaultMessage = defMsgAnnot.value(); |
| } else if (isAnnotationPresent(DefaultStringMapValue.class)) { |
| messageStyle = MessageStyle.PLAIN; |
| processStringMap(getAnnotation(DefaultStringMapValue.class)); |
| isStringMap = true; |
| } else { |
| messageStyle = MessageStyle.PLAIN; |
| defaultMessage = MessageUtils.getConstantsDefaultValue(this); |
| } |
| Meaning meaningAnnot = getAnnotation(Meaning.class); |
| if (meaningAnnot != null) { |
| meaning = meaningAnnot.value(); |
| } else { |
| meaning = null; |
| } |
| if (overrideDefault == null) { |
| // if the external source has a default entry, use it for the base |
| // message. |
| overrideDefault = getTranslation(localeFactory.getDefault()); |
| if (overrideDefault == this) { |
| overrideDefault = null; |
| } |
| } |
| List<Parameter> params = getParameters(); |
| int[] selectorIndices = getSelectorParameterIndices(); |
| int numSelectors = selectorIndices.length; |
| defaultForms = new ArrayList<AlternateForm>(); |
| selectors = new AlternateMessageSelector[numSelectors]; |
| for (int i = 0; i < numSelectors; ++i) { |
| int selIdx = selectorIndices[i]; |
| if (selIdx < 0) { |
| // string map |
| selectors[i] = new AlternateMessageSelector() { |
| public boolean isFormAcceptable(String form) { |
| return true; |
| } |
| }; |
| } else { |
| selectors[i] = params.get(selIdx).getAlternateMessageSelector(); |
| defaultForms.add(AlternateMessageSelector.OTHER_FORM); |
| } |
| } |
| } |
| |
| protected boolean isStringMap() { |
| return isStringMap; |
| } |
| |
| private void ensureSelectorParams() { |
| if (isAnnotationPresent(DefaultStringMapValue.class)) { |
| selectorParams = new int[] { -1 }; |
| return; |
| } |
| List<Integer> selectorIdx = new ArrayList<Integer>(); |
| List<Parameter> params = getParameters(); |
| int n = params.size(); |
| for (int i = 0; i < n; ++i) { |
| Parameter param = params.get(i); |
| if (param.isAnnotationPresent(PluralCount.class) |
| || param.isAnnotationPresent(Select.class)) { |
| selectorIdx.add(i); |
| } |
| } |
| n = selectorIdx.size(); |
| selectorParams = new int[n]; |
| for (int i = 0; i < n; ++i) { |
| selectorParams[i] = selectorIdx.get(i); |
| } |
| } |
| |
| private void processStringMap(DefaultStringMapValue dsmv) { |
| String[] keyValues = dsmv.value(); |
| StringBuilder buf = new StringBuilder(); |
| boolean needComma = false; |
| Map<String, String> map = new HashMap<String, String>(); |
| List<String> sortedKeys = new ArrayList<String>(); |
| for (int i = 0; i < keyValues.length; i += 2) { |
| sortedKeys.add(keyValues[i]); |
| map.put(keyValues[i], keyValues[i + 1]); |
| if (needComma) { |
| buf.append(','); |
| } else { |
| needComma = true; |
| } |
| buf.append(MessageUtils.quoteComma(keyValues[i])); |
| } |
| defaultMessage = buf.toString(); |
| // sets overrideDefault, but this may be reset if there is an external |
| // translation for the default locale for this map |
| Collections.sort(sortedKeys); |
| overrideDefault = new StringMapMessageTranslation(defaultMessage, |
| sortedKeys, map, matchedLocale); |
| } |
| } |