org.jboss.tools.jst.jsp.bundle.BundleMap.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.tools.jst.jsp.bundle.BundleMap.java

Source

/*******************************************************************************
 * Copyright (c) 2007-2012 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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
 *
 * Contributors:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.jboss.tools.jst.jsp.bundle;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.jboss.tools.common.el.core.model.ELArgumentInvocation;
import org.jboss.tools.common.el.core.model.ELExpression;
import org.jboss.tools.common.el.core.model.ELInstance;
import org.jboss.tools.common.el.core.model.ELInvocationExpression;
import org.jboss.tools.common.el.core.model.ELModel;
import org.jboss.tools.common.el.core.model.ELPropertyInvocation;
import org.jboss.tools.common.el.core.parser.ELParser;
import org.jboss.tools.common.el.core.parser.ELParserUtil;
import org.jboss.tools.common.model.XModel;
import org.jboss.tools.common.model.event.XModelTreeEvent;
import org.jboss.tools.common.model.event.XModelTreeListener;
import org.jboss.tools.common.model.options.PreferenceModelUtilities;
import org.jboss.tools.common.model.project.IModelNature;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.jst.jsp.JspEditorPlugin;
import org.jboss.tools.jst.jsp.i18n.MainLocaleProvider;
import org.jboss.tools.jst.jsp.preferences.IVpePreferencesPage;
import org.jboss.tools.jst.jsp.util.Constants;
import org.jboss.tools.jst.web.project.WebProject;
import org.jboss.tools.jst.web.project.list.WebPromptingProvider;

public class BundleMap {

    public static final String TITLE_ATTRIBUTE_NAME = "title"; //$NON-NLS-1$
    private static final String[] JSF_PROJECT_NATURES = { WebProject.JSF_NATURE_ID };

    private BundleMapListener[] bundleMapListeners = new BundleMapListener[0];
    private String[] javaSources;
    /*
     * Stores the current VPE locale.
     */
    private Locale locale;
    private BundleEntry[] bundles = new BundleEntry[0];
    private Map<String, UsedKey> usedKeys = new HashMap<String, UsedKey>();

    boolean showBundleUsageAsEL = JspEditorPlugin.getDefault().getPreferenceStore()
            .getBoolean(IVpePreferencesPage.SHOW_RESOURCE_BUNDLES_USAGE_AS_EL);

    XModelTreeListener modelListener = new ML();
    private IProject project;

    public void init(IEditorInput input) {
        if (input instanceof IFileEditorInput) {
            project = ((IFileEditorInput) input).getFile().getProject();
        }
        init(project);
    }

    public void init(IProject project) {
        if (project != null) {
            javaSources = getJavaProjectSrcLocations(project);
        }
        /*
         * Initialize the locale with default value.
         */
        locale = MainLocaleProvider.getInstance().getLocale(project);
        refreshRegisteredBundles();
        PreferenceModelUtilities.getPreferenceModel().addModelTreeListener(modelListener);
    }

    public void refreshRegisteredBundles() {
        if (hasJsfProjectNatureType() && project != null) {
            IModelNature modelNature = EclipseResourceUtil.getModelNature(project);
            if (modelNature != null) {
                XModel model = modelNature.getModel();
                List<Object> l = WebPromptingProvider.getInstance().getList(model,
                        WebPromptingProvider.JSF_REGISTERED_BUNDLES, null, null);
                if (l != null && l.size() > 1 && (l.get(1) instanceof Map)) {
                    Map<?, ?> map = (Map<?, ?>) l.get(1);
                    /*
                     * Fix for https://jira.jboss.org/jira/browse/JBIDE-5218
                     * When updating f:view's locale attribute right after
                     * template creation - map of registered bundles is empty
                     * and couldn't be updated. To change bundle's locale they
                     * should be accessed through <code>bundles</code> variable.
                     */
                    if (map.keySet().size() > 0) {
                        Iterator<?> it = map.keySet().iterator();
                        while (it.hasNext()) {
                            String uri = it.next().toString();
                            String prefix = map.get(uri).toString();
                            int hash = (prefix + ":" + uri).hashCode(); //$NON-NLS-1$
                            removeBundle(hash);
                            addBundle(hash, prefix, uri, true);
                        }
                    } else if (bundles.length > 0) {
                        for (int i = 0; i < bundles.length; i++) {
                            String uri = bundles[i].uri;
                            String prefix = bundles[i].prefix;
                            int hash = (prefix + ":" + uri).hashCode(); //$NON-NLS-1$
                            removeBundle(hash);
                            addBundle(hash, prefix, uri, true);
                        }
                    }
                }
            }
        }
    }

    public void clearAll() {
        bundles = new BundleEntry[0];
        usedKeys = new HashMap<String, UsedKey>();
    }

    public void dispose() {
        PreferenceModelUtilities.getPreferenceModel().removeModelTreeListener(modelListener);
    }

    public boolean isShowBundleUsageAsEL() {
        return showBundleUsageAsEL;
    }

    private boolean hasJsfProjectNatureType() {
        boolean hasJsfProjectNatureType = false;
        try {
            if (project != null) {
                if (project.exists() && project.isOpen()) {
                    for (int i = 0; i < JSF_PROJECT_NATURES.length; i++) {
                        if (project.hasNature(JSF_PROJECT_NATURES[i])) {
                            hasJsfProjectNatureType = true;
                            break;
                        }
                    }
                }
            }
        } catch (CoreException e) {
            JspEditorPlugin.getPluginLog().logError(e);
        }
        return hasJsfProjectNatureType;
    }

    public boolean openBundle(String expression, String locale) {
        List<ELInstance> is = parseJSFExpression(expression);
        if (is == null || is.size() == 0)
            return false;
        String prefix = null;
        String propertyName = null;
        for (ELInstance i : is) {
            ELExpression expr = i.getExpression();
            if (expr == null)
                continue;
            List<ELInvocationExpression> invs = expr.getInvocations();
            if (invs.size() > 0) {
                String[] values = getCall(invs.get(0));
                if (values != null) {
                    prefix = values[0];
                    propertyName = values[1];
                    break;
                }
            }
        }
        if (prefix == null)
            return false;

        BundleEntry entry = getBundle(prefix);

        if (entry == null) {
            if (hasJsfProjectNatureType()) {
                if (project != null) {
                    XModel model = EclipseResourceUtil.getModelNature(project).getModel();
                    String prefix2 = prefix;
                    if (propertyName != null && prefix != null) {
                        prefix2 = prefix + "." + propertyName; //$NON-NLS-1$
                    }
                    WebPromptingProvider.getInstance().getList(model, WebPromptingProvider.JSF_BEAN_OPEN, prefix2,
                            null);
                }
            }
            return false;
        }

        if (hasJsfProjectNatureType()) {
            Properties p = new Properties();
            p.put(WebPromptingProvider.BUNDLE, entry.uri);
            p.put(WebPromptingProvider.KEY, propertyName);
            if (locale != null)
                p.put(WebPromptingProvider.LOCALE, locale);
            p.put(WebPromptingProvider.FILE, project);
            String error = null;
            if (project != null) {
                XModel model = EclipseResourceUtil.getModelNature(project).getModel();
                WebPromptingProvider.getInstance().getList(model, WebPromptingProvider.JSF_OPEN_KEY, entry.uri, p);
                error = p.getProperty(WebPromptingProvider.ERROR);
            }
            return (error == null || error.length() == 0);
        }
        return false;
    }

    /**
     * Gets the bundle file based on the locale 
     * from the loaded resource bundle.
     *
     * @param uri the uri
     * @return the bundle file
     */
    public IFile getBundleFile(String uri) {
        if (project == null || !project.isOpen()) {
            return null;
        }
        try {
            if (!project.hasNature(JavaCore.NATURE_ID)) {
                return null;
            }
            IJavaProject javaProject = JavaCore.create(project);
            IClasspathEntry[] es = javaProject.getResolvedClasspath(true);
            for (int i = 0; i < es.length; i++) {
                if (es[i].getEntryKind() != IClasspathEntry.CPE_SOURCE) {
                    continue;
                }
                IFile file = (IFile) project.getWorkspace().getRoot().getFolder(es[i].getPath())
                        .findMember("/" + getBundleFileName(uri)); //$NON-NLS-1$
                if (file != null && file.exists()) {
                    return file;
                }
            }
        } catch (CoreException e) {
            JspEditorPlugin.getPluginLog().logError(e);
            return null;
        }
        return null;
    }

    /**
     * Gets the bundle file name.
     * <a href="https://jira.jboss.org/browse/JBIDE-6729">
     * Related Jira</a>
     *
     * @param uri the uri
     * @return the bundle file name
     */
    private String getBundleFileName(String uri) {
        String resultUri = uri.replace('.', '/');
        ResourceBundle bundle = getBundleByUrl(uri, locale);
        String localeString = bundle.getLocale().toString();
        if ((null != localeString) && (localeString.length() > 0)) {
            /*
             * getLanguage() method in  ResourceBundle could return "en_us" string.
             * Bundle's file is case sensitive thus country name 
             * should be transformed to UpperCase. 
             */
            String[] parts = localeString.split(Constants.UNDERSCORE);
            if (parts.length == 2) {
                parts[1] = parts[1].toUpperCase();
            }
            for (String part : parts) {
                resultUri += Constants.UNDERSCORE + part;
            }
        }
        resultUri += ".properties"; //$NON-NLS-1$
        return resultUri;
    }

    private ResourceBundle getBundleByUrl(String uri, Locale locale) {
        try {
            if (javaSources != null) {
                File file;
                URL[] urls = new URL[javaSources.length];
                for (int i = 0; i < javaSources.length; ++i) {
                    try {
                        file = new File(javaSources[i]).getCanonicalFile();
                        urls[i] = file.toURL();
                    } catch (IOException ioe) {
                        JspEditorPlugin.getDefault().logError(ioe);
                        return null;
                    }
                }

                ClassLoader classLoader = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
                ResourceBundle bundle = ResourceBundle.getBundle(uri, locale, classLoader);
                return bundle;
            }
        } catch (MissingResourceException ex) {
            // Ignore this exception
        }

        return null;
    }

    private static String[] getJavaProjectSrcLocations(IProject project) {
        return EclipseResourceUtil.getJavaProjectSrcLocations(project);
    }

    private void removeBundle(int hashCode, boolean refresh) {
        if (bundles.length == 0) {
            return;
        }
        int index = -1;
        for (int i = 0; i < bundles.length; i++) {
            if (hashCode == bundles[i].hashCode) {
                index = i;
                break;
            }
        }
        if (index == -1) {
            return;
        }
        if (bundles.length == 1) {
            bundles = new BundleEntry[0];
            return;
        }
        BundleEntry[] newBundles = new BundleEntry[bundles.length - 1];
        System.arraycopy(bundles, 0, newBundles, 0, index);
        System.arraycopy(bundles, index + 1, newBundles, index, bundles.length - index - 1);
        bundles = newBundles;
        if (refresh) {
            refreshUsedKeys();
        }
    }

    public void removeBundle(int hashCode) {
        removeBundle(hashCode, true);
    }

    public void addBundle(int hashCode, String prefix, String uri, boolean refresh) {
        ResourceBundle bundle = getBundleByUrl(uri, locale);
        BundleEntry entry = new BundleEntry(bundle, uri, prefix, hashCode);
        if (bundle != null) {
            BundleEntry[] newBundles = new BundleEntry[bundles.length + 1];
            System.arraycopy(bundles, 0, newBundles, 0, bundles.length);
            bundles = newBundles;
            bundles[bundles.length - 1] = entry;
        }
        if (refresh) {
            refreshUsedKeys();
        }
    }

    public void changeBundle(int hashCode, String prefix, String uri) {
        removeBundle(hashCode, false);
        addBundle(hashCode, prefix, uri, true);
    }

    private void changeBundleWithoutRefresh(int hashCode, String prefix, String uri) {
        removeBundle(hashCode, false);
        addBundle(hashCode, prefix, uri, false);
    }

    private BundleEntry getBundle(String prefix) {
        if (prefix == null) {
            return null;
        }
        BundleEntry lastBundle = null;
        for (int i = 0; i < bundles.length; i++) {
            if (prefix.equals(bundles[i].prefix)) {
                lastBundle = bundles[i];
            }
        }
        return lastBundle;
    }

    public void refresh() {
        refreshRegisteredBundles();
        if (project != null) {
            javaSources = getJavaProjectSrcLocations(project);
            UsedKey key;
            UsedKey[] array = new UsedKey[0];
            array = usedKeys.values().toArray(array);

            for (int i = 0; i < array.length; i++) {
                key = (UsedKey) array[i];
                changeBundleWithoutRefresh(key.hashCode, key.prefix, key.uri);
            }
            refreshUsedKeys();
        }
    }

    private void refreshUsedKeys() {
        UsedKey keyValue;

        /* yradtsevich: Fix of JBIDE-5818. The map usedKey cannot be modified
         * in the following foreach loop. Therefore the keys to remove
         * are marked and removed after the loop. */
        List<String> keysToBeRemoved = new ArrayList<String>(0);

        Set<String> usedKeysSet = this.usedKeys.keySet();
        for (String key : usedKeysSet) {
            keyValue = this.usedKeys.get(key);
            BundleEntry entry = getBundle(keyValue.prefix);
            if (entry != null) {
                String value;
                try {
                    value = (String) entry.bundle.getObject(keyValue.key);
                } catch (MissingResourceException ex) {
                    value = null;
                    fireBundleKeyChanged(keyValue.prefix, keyValue.key, value);
                    // Fix of JBIDE-5818
                    keysToBeRemoved.add(key);
                    continue;
                }
                if ((value == null && keyValue.value != null) || (value != null && keyValue.value == null)) {
                    keyValue.value = value;
                    fireBundleKeyChanged(keyValue.prefix, keyValue.key, value);
                    continue;
                } else if (value != null && keyValue.value != null && !value.equals(keyValue.value)) {
                    keyValue.value = value;
                    fireBundleKeyChanged(keyValue.prefix, keyValue.key, value);
                    continue;
                }
            } else {
                keyValue.value = null;
                fireBundleKeyChanged(keyValue.prefix, keyValue.key, null);
            }
        }

        // Fix of JBIDE-5818
        for (String key : keysToBeRemoved) {
            this.usedKeys.remove(key);
        }
    }

    private List<ELInstance> parseJSFExpression(String expression) {
        ELParser parser = ELParserUtil.getDefaultFactory().createParser();
        ELModel model = parser.parse(expression);
        List<ELInstance> is = model.getInstances();
        return is;
    }

    public String getBundleValue(String name) {
        String bundleValue = name;
        if (!showBundleUsageAsEL) {
            List<ELInstance> is = parseJSFExpression(name);
            if (is != null) {
                StringBuffer sb = new StringBuffer();
                int index = 0;
                boolean parsingErrors = false;
                for (ELInstance i : is) {
                    /*
                     * https://issues.jboss.org/browse/JBIDE-10531
                     * Getting the bundle value should not succeed 
                     * if the EL is missing any required token.
                     */
                    if (!i.getErrors().isEmpty()) {
                        parsingErrors = true;
                        break;
                    }
                    int start = i.getStartPosition();
                    sb.append(name.substring(index, start));
                    index = start;
                    if (i.getExpression() instanceof ELInvocationExpression) {
                        ELInvocationExpression expr = (ELInvocationExpression) i.getExpression();
                        String[] values = getCall(expr);
                        if (values != null) {
                            String value = getBundleValue(values[0], values[1]);
                            if (value != null) {
                                sb.append(value);
                                index = i.getEndPosition();
                            }
                        }
                    }
                    if (index < i.getEndPosition()) {
                        /*
                         * https://jira.jboss.org/jira/browse/JBIDE-6064
                         * CA out of range error occurs sometimes
                         */
                        if (name.length() > i.getEndPosition()) {
                            sb.append(name.substring(index, i.getEndPosition()));
                            index = i.getEndPosition();
                        } else {
                            sb.append(name.substring(index, name.length()));
                            index = name.length();
                        }
                    }
                }
                if (!parsingErrors) {
                    bundleValue = sb.append(name.substring(index)).toString();
                }
            }
        }
        return bundleValue;
    }

    String[] getCall(ELInvocationExpression expr) {
        if (expr == null)
            return null;
        ELInvocationExpression left = expr.getLeft();
        if (left == null)
            return null;
        String name = expr.getMemberName();
        if (name == null || name.length() == 0)
            return null;
        if (expr instanceof ELPropertyInvocation) {
            /*
             * Simple EL like #{bundle.key} goes here
             */
            return new String[] { left.getText(), name };
        } else if (expr instanceof ELArgumentInvocation) {
            /*
             * EL like #{bundle['compound.key']} goes here
             */
            if (name.startsWith("\"") || name.startsWith("'")) { //$NON-NLS-1$ //$NON-NLS-2$
                name = name.substring(1);
            } else {
                /*
                 * https://issues.jboss.org/browse/JBIDE-10531
                 * EL with no quotes cannot be used to determine the bundle value.
                 */
                return null;
            }
            if (name.endsWith("\"") || name.endsWith("'")) { //$NON-NLS-1$ //$NON-NLS-2$
                name = name.substring(0, name.length() - 1);
            } else {
                /*
                 * https://issues.jboss.org/browse/JBIDE-10531
                 * EL with no quotes cannot be used to determine the bundle value.
                 */
                return null;
            }
            if (name.length() == 0) {
                return null;
            }
            return new String[] { left.getText(), name };
        }
        return null;
    }

    private String getBundleValue(String prefix, String propertyName) {
        String bundleValue = null;
        BundleEntry entry = getBundle(prefix);
        if (entry != null) {
            String name = prefix + "." + propertyName; //$NON-NLS-1$
            try {
                bundleValue = (String) entry.bundle.getObject(propertyName);
                if (!usedKeys.containsKey(name))
                    usedKeys.put(name, new UsedKey(entry.uri, prefix, propertyName, bundleValue, entry.hashCode));
            } catch (MissingResourceException ex) {
                /*
                 * Null string will be returned.
                 */
            }
        }
        return bundleValue;
    }

    public void addBundleMapListener(BundleMapListener listener) {
        if (listener != null) {
            BundleMapListener[] newBundleMapListener = new BundleMapListener[bundleMapListeners.length + 1];
            System.arraycopy(bundleMapListeners, 0, newBundleMapListener, 0, bundleMapListeners.length);
            bundleMapListeners = newBundleMapListener;
            bundleMapListeners[bundleMapListeners.length - 1] = listener;
        }
    }

    public void removeBundleMapListener(BundleMapListener listener) {
        if (listener == null || bundleMapListeners.length == 0)
            return;
        int index = -1;
        for (int i = 0; i < bundleMapListeners.length; i++) {
            if (listener == bundleMapListeners[i]) {
                index = i;
                break;
            }
        }
        if (index == -1)
            return;
        if (bundleMapListeners.length == 1) {
            bundleMapListeners = new BundleMapListener[0];
            return;
        }
        BundleMapListener[] newBundleMapListener = new BundleMapListener[bundleMapListeners.length - 1];
        System.arraycopy(bundleMapListeners, 0, newBundleMapListener, 0, index);
        System.arraycopy(bundleMapListeners, index + 1, newBundleMapListener, index,
                bundleMapListeners.length - index - 1);
        bundleMapListeners = newBundleMapListener;
    }

    private void fireBundleKeyChanged(String prefix, String key, String value) {
        for (int i = 0; i < bundleMapListeners.length; i++) {
            bundleMapListeners[i].bundleKeyChanged(prefix, key, value);
        }
    }

    public void updateShowBundleUsageAsEL(boolean showBundlesAsEL) {
        if (showBundleUsageAsEL != showBundlesAsEL) {
            showBundleUsageAsEL = showBundlesAsEL;
            refresh();
        }
    }

    public void updateShowBundleUsageAsEL() {
        updateShowBundleUsageAsEL(JspEditorPlugin.getDefault().getPreferenceStore()
                .getBoolean(IVpePreferencesPage.SHOW_RESOURCE_BUNDLES_USAGE_AS_EL));
    }

    public Locale getLocale() {
        return locale;
    }

    public void setLocale(Locale locale) {
        this.locale = locale;
    }

    static class Expression {
        public String prefix;
        public String propertyName;
    }

    /*
     * https://jira.jboss.org/browse/JBIDE-6287
     * It was required to get access to BundleEntry class.
     */
    public static class BundleEntry {
        public ResourceBundle bundle;
        public String uri;
        public String prefix;
        public int hashCode;

        public BundleEntry(ResourceBundle bundle, String uri, String prefix, int hashCode) {
            this.bundle = bundle;
            this.uri = uri;
            this.prefix = prefix;
            this.hashCode = hashCode;
        }

        @Override
        public String toString() {
            return "BundleEntry [prefix=" + prefix + ", uri=" + uri + ", hashCode=" + hashCode //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                    + ", bundle=" + bundle + "]"; //$NON-NLS-1$ //$NON-NLS-2$
        }

    }

    static class UsedKey {
        public int hashCode;
        public String uri;
        public String prefix;
        public String key;
        public String value;

        public UsedKey(String uri, String prefix, String key, String value, int hashCode) {
            this.uri = uri;
            this.prefix = prefix;
            this.key = key;
            this.value = value;
            this.hashCode = hashCode;

        }
    }

    class ML implements XModelTreeListener {

        public void nodeChanged(XModelTreeEvent event) {
            updateShowBundleUsageAsEL();
        }

        public void structureChanged(XModelTreeEvent event) {
        }

    }

    /**
     * @param showBundleUsageAsEL the showBundleUsageAsEL to set
     */
    public void setShowBundleUsageAsEL(boolean showBundleUsageAsEL) {
        this.showBundleUsageAsEL = showBundleUsageAsEL;
    }

    public BundleEntry[] getBundles() {
        return bundles;
    }

}