/*******************************************************************************
* Copyright 2011 Google Inc. All Rights Reserved.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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.gdt.eclipse.designer.nls;
import com.google.common.collect.Lists;
import com.google.gdt.eclipse.designer.common.Constants;
import com.google.gdt.eclipse.designer.model.module.ExtendPropertyElement;
import com.google.gdt.eclipse.designer.model.module.InheritsElement;
import com.google.gdt.eclipse.designer.model.module.ModuleElement;
import com.google.gdt.eclipse.designer.util.DefaultModuleProvider;
import com.google.gdt.eclipse.designer.util.DefaultModuleProvider.ModuleModification;
import com.google.gdt.eclipse.designer.util.ModuleDescription;
import com.google.gdt.eclipse.designer.util.Utils;
import org.eclipse.wb.core.model.JavaInfo;
import org.eclipse.wb.internal.core.DesignerPlugin;
import org.eclipse.wb.internal.core.model.JavaInfoUtils;
import org.eclipse.wb.internal.core.model.property.GenericProperty;
import org.eclipse.wb.internal.core.nls.bundle.IPropertiesAccessor;
import org.eclipse.wb.internal.core.nls.bundle.pure.AbstractPureBundleSource;
import org.eclipse.wb.internal.core.nls.edit.IEditableSource;
import org.eclipse.wb.internal.core.nls.model.AbstractSource;
import org.eclipse.wb.internal.core.nls.model.IKeyGeneratorStrategy;
import org.eclipse.wb.internal.core.nls.model.LocaleInfo;
import org.eclipse.wb.internal.core.utils.IOUtils2;
import org.eclipse.wb.internal.core.utils.ast.AstEditor;
import org.eclipse.wb.internal.core.utils.ast.AstNodeUtils;
import org.eclipse.wb.internal.core.utils.ast.AstVisitorEx;
import org.eclipse.wb.internal.core.utils.ast.BodyDeclarationTarget;
import org.eclipse.wb.internal.core.utils.jdt.core.CodeUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Source for GWT Constants class.
*
* @author scheglov_ke
* @coverage gwt.nls
*/
public class GwtSource extends AbstractPureBundleSource {
////////////////////////////////////////////////////////////////////////////
//
// Static fields
//
////////////////////////////////////////////////////////////////////////////
/**
* Key generator for GWT sources.
*/
public static final IKeyGeneratorStrategy GWT_KEY_GENERATOR = new IKeyGeneratorStrategy() {
public final String generateBaseKey(JavaInfo component, GenericProperty property) {
String propertyTitle = property.getTitle().replace(' ', '_');
String componentName = component.getVariableSupport().getComponentName();
return componentName + "_" + propertyTitle;
}
};
////////////////////////////////////////////////////////////////////////////
//
// Possible sources
//
////////////////////////////////////////////////////////////////////////////
private static final String BUNDLE_COMMENT = "GWT variable: ";
@Override
protected String getBundleComment() {
return BUNDLE_COMMENT + m_fieldName;
}
/**
* Return "possible" sources that exist in given package.
*
* "Possible" source is source that exists in current package, but is not used in current unit. We
* show "possible" sources only if there are no "real" sources.
*/
public static List<AbstractSource> getPossibleSources(JavaInfo root, IPackageFragment pkg)
throws Exception {
List<AbstractSource> sources = Lists.newArrayList();
for (IJavaElement packageElement : pkg.getChildren()) {
ICompilationUnit unit = (ICompilationUnit) packageElement;
// prepare IType
IType type = CodeUtils.findPrimaryType(unit);
if (type == null) {
continue;
}
// check that type is is successor of Constants
if (!CodeUtils.isSuccessorOf(type, Constants.CLASS_CONSTANTS)) {
continue;
}
// prepare field name for Constants instance
// it should be on the first line of default *.properties file
String fieldName;
{
IFolder folder = (IFolder) type.getPackageFragment().getUnderlyingResource();
IFile defaultPropertiesFile = folder.getFile(type.getElementName() + ".properties");
if (!defaultPropertiesFile.exists()) {
continue;
}
// check first line for required comment
InputStream is = defaultPropertiesFile.getContents(true);
String firstLine = IOUtils2.readFirstLine(is);
if (firstLine != null && firstLine.startsWith("#" + BUNDLE_COMMENT)) {
fieldName = firstLine.substring(1 + BUNDLE_COMMENT.length());
} else {
fieldName = "CONSTANTS";
}
}
// OK, this is probably correct source
try {
String bundleName = type.getFullyQualifiedName();
AbstractSource source = new GwtSource(root, bundleName, fieldName);
sources.add(source);
} catch (Throwable e) {
DesignerPlugin.log(e);
}
}
return sources;
}
@Override
public void attachPossible() throws Exception {
addField(m_root, m_bundleName, m_fieldName);
}
////////////////////////////////////////////////////////////////////////////
//
// Creation
//
////////////////////////////////////////////////////////////////////////////
/**
* Parse given expression and return NLSSource for it (new or existing from list).
*/
public static AbstractSource get(JavaInfo component,
GenericProperty property,
Expression expression,
List<AbstractSource> sources) throws Exception {
ExpressionInfo expressionInfo = getExpressionInfo(component, expression);
if (expressionInfo != null) {
GwtSource source = getNewOrExistingSource(component, expressionInfo, sources);
source.onKeyAdd(component, expressionInfo.m_key);
return source;
}
return null;
}
/**
* Find existing source with same field or create new one.
*/
private static GwtSource getNewOrExistingSource(JavaInfo component,
ExpressionInfo expressionInfo,
List<AbstractSource> sources) throws Exception {
for (AbstractSource abstractSource : sources) {
if (abstractSource instanceof GwtSource) {
GwtSource source = (GwtSource) abstractSource;
if (source.m_bundleName.equals(expressionInfo.m_bundleName)) {
return source;
}
}
}
//
return new GwtSource(component.getRootJava(),
expressionInfo.m_bundleName,
expressionInfo.m_fieldName);
}
/**
* Parse given expression and if it is valid GWT NLS expression, extract required information.
*/
private static ExpressionInfo getExpressionInfo(JavaInfo component, Expression expression) {
if (expression instanceof MethodInvocation) {
MethodInvocation invocation = (MethodInvocation) expression;
// check invocation
if (invocation.arguments().size() != 0) {
return null;
}
if (!(invocation.getExpression() instanceof SimpleName)) {
return null;
}
//
SimpleName field = (SimpleName) invocation.getExpression();
String bundleName = AstNodeUtils.getFullyQualifiedName(field, true);
String fieldName = field.getIdentifier();
//
SimpleName keyExpression = invocation.getName();
String key = keyExpression.getIdentifier();
//
ExpressionInfo expressionInfo =
new ExpressionInfo(expression, bundleName, fieldName, keyExpression, key);
expression.setProperty(NLS_EXPRESSION_INFO, expressionInfo);
return expressionInfo;
}
return null;
}
////////////////////////////////////////////////////////////////////////////
//
// Expression information
//
////////////////////////////////////////////////////////////////////////////
/**
* Information about expression. We store it in expression property to avoid parsing every time.
*/
protected static class ExpressionInfo extends BasicExpressionInfo {
private final String m_bundleName;
private final String m_fieldName;
public ExpressionInfo(Expression expression,
String bundleName,
String fieldName,
Expression keyExpression,
String key) {
super(expression, keyExpression, key);
m_bundleName = bundleName;
m_fieldName = fieldName;
}
}
////////////////////////////////////////////////////////////////////////////
//
// Instance fields
//
////////////////////////////////////////////////////////////////////////////
private final String m_fieldName;
private final AstEditor m_accessorEditor;
////////////////////////////////////////////////////////////////////////////
//
// Constructor
//
////////////////////////////////////////////////////////////////////////////
public GwtSource(JavaInfo root, String bundleName, String fieldName) throws Exception {
super(root, bundleName);
m_fieldName = fieldName;
// prepare AST editor for Constants class
{
IType constants_type = m_root.getEditor().getJavaProject().findType(m_bundleName);
ICompilationUnit constants_unit = constants_type.getCompilationUnit();
m_accessorEditor = new AstEditor(constants_unit);
}
}
////////////////////////////////////////////////////////////////////////////
//
// Access
//
////////////////////////////////////////////////////////////////////////////
@Override
public String getTypeTitle() throws Exception {
return "Constants in variable/field '" + m_fieldName + "'";
}
@Override
protected IPropertiesAccessor getPropertiesAccessor() {
return GwtPropertiesAccessor.INSTANCE;
}
////////////////////////////////////////////////////////////////////////////
//
// Edit support
//
////////////////////////////////////////////////////////////////////////////
@Override
protected IKeyGeneratorStrategy getKeyGeneratorStrategy() {
return GWT_KEY_GENERATOR;
}
@Override
public void apply_addKey(String key) throws Exception {
addKey(key);
super.apply_addKey(key);
}
@Override
protected BasicExpressionInfo apply_externalize_replaceExpression(GenericProperty property,
String key) throws Exception {
// replace expression
Expression expression = property.getExpression();
key = key.replace('.', '_');
String newSource = m_fieldName + "." + key + "()";
Expression newExpression = m_root.getEditor().replaceExpression(expression, newSource);
// side effect of this invocation is that ExpressionInfo placed in newExpression
return getExpressionInfo(m_root, newExpression);
}
@Override
protected void apply_renameKeys_pre(final Map<String, String> oldToNew) throws Exception {
m_accessorEditor.getAstUnit().accept(new AstVisitorEx() {
@Override
public void postVisitEx(ASTNode node) throws Exception {
if (node instanceof MethodDeclaration) {
MethodDeclaration methodDeclaration = (MethodDeclaration) node;
String methodName = methodDeclaration.getName().getIdentifier();
String newMethodName = oldToNew.get(methodName);
if (newMethodName != null) {
m_accessorEditor.setIdentifier(methodDeclaration.getName(), newMethodName);
}
}
}
});
commitAccessorChanges();
}
@Override
protected Expression apply_renameKey_replaceKeyExpression(AstEditor editor,
Expression keyExpression,
String newKey) throws Exception {
editor.replaceInvocationName((MethodInvocation) keyExpression.getParent(), newKey);
return keyExpression;
}
@Override
protected void apply_internalizeKeys_post(final Set<String> keys) throws Exception {
m_accessorEditor.getAstUnit().accept(new AstVisitorEx() {
@Override
public void postVisitEx(ASTNode node) throws Exception {
if (node instanceof MethodDeclaration) {
MethodDeclaration methodDeclaration = (MethodDeclaration) node;
String methodName = methodDeclaration.getName().getIdentifier();
if (keys.contains(methodName)) {
m_accessorEditor.removeBodyDeclaration(methodDeclaration);
}
}
}
});
}
/**
* Create NLS source for given root and parameters.
*/
public static GwtSource apply_create(IEditableSource editable, JavaInfo root, Object o)
throws Exception {
// prepare parameters
SourceParameters parameters = (SourceParameters) o;
String fullClassName = parameters.m_constant.m_fullClassName;
String fieldName = parameters.m_fieldName;
// create class if it does not exist already
if (!parameters.m_constant.m_exists) {
// create Constants class
{
ensureI18NModule(root);
// prepare class source
String template;
{
template = IOUtils.toString(GwtSource.class.getResourceAsStream("newConstants.jvt"));
template =
StringUtils.replace(template, "%PACKAGE_NAME%", parameters.m_constant.m_packageName);
template =
StringUtils.replace(template, "%CLASS_NAME%", parameters.m_constant.m_className);
}
// create class file
{
String fileName = parameters.m_constant.m_className + ".java";
createFileIfDoesNotExist(parameters.m_constant.m_packageFolder, fileName, template);
}
}
// create property bundle
{
String propertyFileName = parameters.m_constant.m_className + ".properties";
createPropertyBundleFile(parameters.m_constant.m_package, propertyFileName, "UTF-8");
}
}
// add field
addField(root, fullClassName, fieldName);
// create source
return new GwtSource(root, fullClassName, fieldName);
}
@Override
public void apply_addLocale(LocaleInfo locale, Map<String, String> values) throws Exception {
super.apply_addLocale(locale, values);
modifyLocalesSet(locale, true);
}
@Override
public void apply_removeLocale(LocaleInfo locale) throws Exception {
super.apply_removeLocale(locale);
modifyLocalesSet(locale, false);
}
@Override
protected String getCharsetForBundleFiles() {
return "UTF-8";
}
////////////////////////////////////////////////////////////////////////////
//
// Utils
//
////////////////////////////////////////////////////////////////////////////
private static final String I18N_MODULE = "com.google.gwt.i18n.I18N";
/**
* Add field with <code>Constants</code> definition.
*/
private static void addField(JavaInfo root, String fullClassName, String fieldName)
throws Exception {
String code =
"private static final "
+ fullClassName
+ " "
+ fieldName
+ " = com.google.gwt.core.client.GWT.create("
+ fullClassName
+ ".class);";
TypeDeclaration typeDeclaration = JavaInfoUtils.getTypeDeclaration(root);
BodyDeclarationTarget target = new BodyDeclarationTarget(typeDeclaration, true);
root.getEditor().addFieldDeclaration(code, target);
}
/**
* Ensures that module for given compilation unit contains import for module
* "com.google.gwt.i18n.I18N".
*/
private static void ensureI18NModule(JavaInfo root) throws Exception {
ModuleDescription module = getModule(root);
DefaultModuleProvider.modify(module, new ModuleModification() {
public void modify(ModuleElement moduleElement) throws Exception {
// check, may be already import for I18N
for (InheritsElement inheritsElement : moduleElement.getInheritsElements()) {
if (inheritsElement.getName().equals(I18N_MODULE)) {
return;
}
}
// import I18N
{
InheritsElement inheritsElement = new InheritsElement();
moduleElement.addChild(inheritsElement);
inheritsElement.setName(I18N_MODULE);
}
}
});
}
/**
* Adds or removes (depending on value of "add" parameter) given locale into module.
*/
private void modifyLocalesSet(final LocaleInfo locale, final boolean add) throws Exception {
// don't add default locale
if (locale.isDefault()) {
return;
}
//
ModuleDescription module = getModule(m_root);
DefaultModuleProvider.modify(module, new ModuleModification() {
public void modify(ModuleElement moduleElement) throws Exception {
// build list of locales in <extent-property name='locale'> elements, remove these elements
List<String> locales = Lists.newArrayList();
for (ExtendPropertyElement extendPropertyElement : moduleElement.getExtendPropertyElements()) {
if (extendPropertyElement.getName().equals("locale")) {
extendPropertyElement.remove();
String[] parts = StringUtils.split(extendPropertyElement.getValues(), ", ");
CollectionUtils.addAll(locales, parts);
}
}
// add/remove new locale
{
String localeName = locale.getTitle();
if (add) {
locales.add(localeName);
} else {
locales.remove(localeName);
}
}
// sort locales
Collections.sort(locales);
// add new <extent-property> with all locales
{
ExtendPropertyElement extendPropertyElement = new ExtendPropertyElement();
moduleElement.addChild(extendPropertyElement);
extendPropertyElement.setName("locale");
extendPropertyElement.setValues(StringUtils.join(locales.iterator(), ","));
}
}
});
}
/**
* @return the {@link ModuleDescription}, not <code>null</code>.
*/
private static ModuleDescription getModule(JavaInfo root) throws Exception {
ICompilationUnit compilationUnit = root.getEditor().getModelUnit();
return Utils.getSingleModule(compilationUnit);
}
////////////////////////////////////////////////////////////////////////////
//
// Utils
//
////////////////////////////////////////////////////////////////////////////
private void addKey(String key) throws Exception {
TypeDeclaration typeDeclaration =
(TypeDeclaration) m_accessorEditor.getAstUnit().types().get(0);
m_accessorEditor.addInterfaceMethodDeclaration(
"String " + key + "()",
new BodyDeclarationTarget(typeDeclaration, false));
commitAccessorChanges();
}
/**
* Commits changes in accessor {@link AstEditor}, including saving {@link ICompilationUnit}
* buffer.
*/
private void commitAccessorChanges() throws Exception {
m_accessorEditor.commitChanges();
m_accessorEditor.getModelUnit().save(null, true);
}
}