com.google.gdt.eclipse.designer.hosted.tdt.HostedModeSupport.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gdt.eclipse.designer.hosted.tdt.HostedModeSupport.java

Source

/*******************************************************************************
 * 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.hosted.tdt;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.MapMaker;
import com.google.gdt.eclipse.designer.hosted.HostedModeException;
import com.google.gdt.eclipse.designer.hosted.IBrowserShell;
import com.google.gdt.eclipse.designer.hosted.IBrowserShellFactory;
import com.google.gdt.eclipse.designer.hosted.IHostedModeSupport;
import com.google.gdt.eclipse.designer.hosted.ILogSupport;
import com.google.gdt.eclipse.designer.hosted.IModuleDescription;
import com.google.gdt.eclipse.designer.hosted.tdt.log.LogSupport;
import com.google.gwt.dev.shell.designtime.DispatchClassInfo;
import com.google.gwt.dev.shell.designtime.DispatchIdOracle;
import com.google.gwt.dev.shell.designtime.ModuleSpace;

import org.eclipse.wb.internal.core.EnvironmentUtils;
import org.eclipse.wb.internal.core.utils.external.ExternalFactoriesHelper;
import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.swt.widgets.Display;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.osgi.framework.Bundle;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.Map;

/**
 * Implementation for {@link IHostedModeSupport} for GWT. Also used as {@link IBrowserShellHost}
 * while creating {@link ModuleSpace} for current platform.
 * 
 * @author mitin_aa
 * @coverage gwtHosted
 */
public final class HostedModeSupport implements IHostedModeSupport, IBrowserShellHost {
    private static Map<String, ClassLoader> devClassLoaders = new MapMaker().softValues().makeMap();
    private final ClassLoader parentClassLoader;
    private final IModuleDescription moduleDescription;
    private final BrowserShell browserShell;
    private final IJavaProject javaProject;
    private ClassLoader projectClassLoader;
    private Object moduleSpaceHost;
    private final LogSupport logSupport;
    private Object impl;
    private DispatchIdOracle dispatchIdOracle;

    ////////////////////////////////////////////////////////////////////////////
    //
    // Constructor
    //
    ////////////////////////////////////////////////////////////////////////////
    public HostedModeSupport(ClassLoader parentClassLoader, IModuleDescription moduleDescription) throws Exception {
        this.parentClassLoader = parentClassLoader;
        this.moduleDescription = moduleDescription;
        this.javaProject = moduleDescription.getJavaProject();
        // Class loaders
        createClassLoaders();
        // impl
        loadImpl();
        // Logger
        this.logSupport = new LogSupport(3 /*TreeLogger.TRACE*/, impl, javaProject);
        // Browser shell
        this.browserShell = (BrowserShell) createBrowserShell();
        this.browserShell.setHost(this);
    }

    /**
     * Constructor to use for "warm up".
     */
    public HostedModeSupport(IModuleDescription moduleDescription) throws Exception {
        this.parentClassLoader = null;
        this.moduleDescription = moduleDescription;
        this.javaProject = moduleDescription.getJavaProject();
        // Class loaders
        createClassLoaders();
        // impl
        loadImpl();
        // Logger
        this.logSupport = new LogSupport(3 /*TreeLogger.TRACE*/, impl, javaProject);
        // Browser shell
        this.browserShell = null;
    }

    private void loadImpl() throws Exception {
        Class<?> implClass = getDevClassLoader()
                .loadClass("com.google.gwt.dev.shell.designtime.HostedModeSupportImpl");
        impl = implClass.newInstance();
        //
        /*Class<?> moduleSpaceClass =
            getDevClassLoader().loadClass("com.google.gwt.dev.shell.designtime.DelegatingModuleSpace");
        ModuleSpace.setDelegatingModuleSpaceClass(moduleSpaceClass);*/
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // ClassLoaders
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Creates the special {@link ClassLoader}'s to work with GWT "dev" classes and "user" classes.
     */
    private void createClassLoaders() throws Exception {
        if (javaProject == null) {
            projectClassLoader = ClassLoader.getSystemClassLoader();
        } else {
            ClassLoader devClassLoader = getDevClassLoader0();
            projectClassLoader = new LocalProjectClassLoader(moduleDescription.getURLs(), devClassLoader);
        }
    }

    private static final class LocalProjectClassLoader extends URLClassLoader {
        private LocalProjectClassLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent);
        }

        @Override
        public URL findResource(String name) {
            URL url = super.findResource(name);
            if (isWrongURL(url)) {
                url = null;
            }
            return url;
        }

        /**
         * @return <code>true</code> if given {@link URL} represents {@link File} with non-canonical
         *         path, such as using incorrect case on Windows. JDT compiler tried to detect if given
         *         name "test" is name of package or not, by searching for "test.class" resource. But on
         *         Windows file system is not case sensitive, so "Test.class" resource returned, so it
         *         is considered not as package, but as type.
         */
        private boolean isWrongURL(URL url) {
            if (EnvironmentUtils.IS_WINDOWS) {
                File file = FileUtils.toFile(url);
                if (file != null && file.exists()) {
                    try {
                        String absolutePath = file.getAbsolutePath();
                        String canonicalPath = file.getCanonicalPath();
                        return !absolutePath.equals(canonicalPath);
                    } catch (Throwable e) {
                    }
                }
            }
            return false;
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // IHostedModeSupport
    //
    ////////////////////////////////////////////////////////////////////////////
    public void startup(String browserStartupUrl, String moduleName, IProgressMonitor monitor, int timeout)
            throws Exception {
        browserShell.setUrl(browserStartupUrl, moduleName, timeout, new Runnable() {
            public void run() {
                runMessagesLoop();
            }
        });
        // setup parent for CompilingClassLoader
        ClassLoader classLoader = getClassLoader();
        ReflectionUtils.setField(classLoader, "parent", parentClassLoader);
    }

    public void dispose() {
        if (moduleSpaceHost != null) {
            // clear static caches
            ClassLoader devClassLoader = getDevClassLoader();
            try {
                Class<?> clazz = devClassLoader.loadClass("com.google.gwt.i18n.rebind.ClearStaticData");
                ReflectionUtils.invokeMethod2(clazz, "clear");
            } catch (Throwable e) {
            }
            try {
                Class<?> clazz = devClassLoader.loadClass("com.google.gwt.uibinder.rebind.model.OwnerFieldClass");
                Map<?, ?> map = (Map<?, ?>) ReflectionUtils.getFieldObject(clazz, "FIELD_CLASSES");
                map.clear();
            } catch (Throwable e) {
            }
            // remove parent of CompilingClassLoader
            if (parentClassLoader != null) {
                ClassLoader classLoader = getClassLoader();
                ReflectionUtils.setField(classLoader, "parent", null);
            }
            // clear "loadedModulesCaches" in com.google.gwt.dev.cfg.ModuleDefLoader
            try {
                Class<?> moduleDefLoader = devClassLoader.loadClass("com.google.gwt.dev.cfg.ModuleDefLoader");
                Map<?, ?> loadedModulesCaches = (Map<?, ?>) ReflectionUtils.getFieldObject(moduleDefLoader,
                        "loadedModulesCaches");
                loadedModulesCaches.clear();
            } catch (Throwable e) {
            }
            /*// clear "threadLocalLogger" in com.google.gwt.dev.shell.ModuleSpace
            try {
              Class<?> classModuleSpace =
                  devClassLoader.loadClass("com.google.gwt.dev.shell.ModuleSpace");
              ThreadLocal<?> threadLocalLogger =
                  (ThreadLocal<?>) ReflectionUtils.getFieldObject(classModuleSpace, "threadLocalLogger");
              threadLocalLogger.set(null);
            } catch (Throwable e) {
            }
            // shutdown com.google.gwt.dev.javac.PersistentUnitCache
            try {
              Class<?> classUnitCacheFactory =
                  devClassLoader.loadClass("com.google.gwt.dev.javac.UnitCacheFactory");
              Object cacheInstance = ReflectionUtils.getFieldObject(classUnitCacheFactory, "instance");
              if (cacheInstance != null) {
                Method shutdownMethod =
                    ReflectionUtils.getMethodBySignature(cacheInstance.getClass(), "shutdown()");
                if (shutdownMethod != null) {
                  shutdownMethod.invoke(cacheInstance);
                }
              }
              ReflectionUtils.setField(classUnitCacheFactory, "instance", null);
            } catch (Throwable e) {
            }
            // Call and remove GWT related java.lang.ApplicationShutdownHooks
            try {
              Class<?> hooksClass =
                  ClassLoader.getSystemClassLoader().loadClass("java.lang.ApplicationShutdownHooks");
              Field hooksField = ReflectionUtils.getFieldByName(hooksClass, "hooks");
              @SuppressWarnings("unchecked")
              Map<Thread, ?> hooks = (Map<Thread, ?>) hooksField.get(null);
              List<Thread> threads = ImmutableList.copyOf(hooks.keySet());
              for (Thread thread : threads) {
                ClassLoader contextClassLoader = thread.getContextClassLoader();
                if (contextClassLoader == devClassLoader) {
                  thread.setContextClassLoader(ClassLoader.getSystemClassLoader());
                  thread.run();
                  hooks.remove(thread);
                }
              }
            } catch (Throwable e) {
              e.printStackTrace();
            }
            // close com.google.gwt.dev.util.DiskCache
            try {
              Class<?> classDiskCache = devClassLoader.loadClass("com.google.gwt.dev.util.DiskCache");
              Object cacheInstance = ReflectionUtils.getFieldObject(classDiskCache, "INSTANCE");
              ReflectionUtils.invokeMethod(cacheInstance, "close()");
            } catch (Throwable e) {
            }
            // find embedded Guava Finalizer and clear reference of our "dev" URLClassLoader
            try {
              Thread[] threads = getAllThreads();
              for (Thread thread : threads) {
                if (thread != null && thread.getContextClassLoader() == devClassLoader) {
                  thread.setContextClassLoader(null);
                }
              }
            } catch (Throwable e) {
            }*/
        }
        //
        if (browserShell != null) {
            browserShell.dispose();
        }
        logSupport.dispose();
        moduleSpaceHost = null;
        impl = null;
        projectClassLoader = null;
        dispatchIdOracle = null;
    }

    /**
     * @return array of {@link Thread}s, may be with <code>null</code> on the end.
     */
    private static Thread[] getAllThreads() {
        // prepare root ThreadGroup
        ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
        ThreadGroup parentGroup;
        while ((parentGroup = rootGroup.getParent()) != null) {
            rootGroup = parentGroup;
        }
        // fill Thread array
        Thread[] threads = new Thread[rootGroup.activeCount()];
        while (rootGroup.enumerate(threads, true) == threads.length) {
            threads = new Thread[threads.length * 2];
        }
        return threads;
    }

    public IBrowserShell getBrowserShell() {
        return browserShell;
    }

    public ClassLoader getClassLoader() {
        // returns CompilingClassLoader
        try {
            return (ClassLoader) ReflectionUtils.invokeMethod2(moduleSpaceHost, "getClassLoader");
        } catch (Throwable e) {
            throw ReflectionUtils.propagate(e);
        }
    }

    public ClassLoader getDevClassLoader() {
        return projectClassLoader;
    }

    public void invalidateRebind(String typeName) {
        try {
            browserShell.getModuleSpace().invalidateRebind(typeName);
        } catch (Throwable e) {
            ReflectionUtils.propagate(e);
        }
    }

    public Object findJType(String name) {
        try {
            return ReflectionUtils.invokeMethod(impl, "findJType(java.lang.String)", name);
        } catch (Throwable e) {
            throw ReflectionUtils.propagate(e);
        }
    }

    /**
     * @return the {@link ClassLoader} for accessing gwt-dev classes mixed with design-time support
     *         lib.
     */
    private ClassLoader getDevClassLoader0() throws Exception {
        // prepare gwt-dev.jar location
        String devLibLocation = Utils.getDevLibLocation(moduleDescription);
        if (devLibLocation == null) {
            throw new HostedModeException(HostedModeException.NO_DEV_LIB);
        }
        String gwtLocation = FilenameUtils.getFullPath(devLibLocation);
        // add 'dev' & 'dev-designtime'
        ClassLoader devClassLoader = devClassLoaders.get(gwtLocation);
        if (devClassLoader == null) {
            URL resolvedDevLibUrl = new File(devLibLocation).toURI().toURL();
            Bundle bundle = Activator.getDefault().getBundle();
            URL devDesignUrl = FileLocator.resolve(bundle.getEntry("/gwt-dev-designtime.jar"));
            if (devDesignUrl != null) {
                // workaround for Issue 258 (https://code.google.com/p/google-plugin-for-eclipse/issues/detail?id=258)
                devDesignUrl = new URL(StringUtils.replace(devDesignUrl.toString(), " ", "%20"));
            }
            devClassLoader = new URLClassLoader(new URL[] { devDesignUrl, resolvedDevLibUrl }, null);
            devClassLoaders.put(gwtLocation, devClassLoader);
        }
        return devClassLoader;
    }

    public void activate() throws Exception {
        // do nothing
    }

    public byte[] getGeneratedResource(String resourceName) throws Exception {
        return null;
    }

    public ILogSupport getLogSupport() {
        return logSupport;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // BrowserShell 
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Creates and returns the {@link IBrowserShell} instance for current platform using external
     * factory.
     */
    private IBrowserShell createBrowserShell() throws Exception {
        List<IBrowserShellFactory> factories = ExternalFactoriesHelper.getElementsInstances(
                IBrowserShellFactory.class, "com.google.gdt.eclipse.designer.hosted.2_2.browserShellFactory",
                "factory");
        for (IBrowserShellFactory factory : factories) {
            IBrowserShell shell = factory.create();
            if (shell != null) {
                return shell;
            }
        }
        // no shell has been created by factories
        if (isWindows64()) {
            // special message for windows
            throw new HostedModeException(HostedModeException.WIN32_NO_WINDOWS_64);
        }
        throw new HostedModeException(HostedModeException.UNSUPPORTED_OS);
    }

    /**
     * @return <code>true</code> while running Windows 64-bit.
     */
    private boolean isWindows64() {
        String osName = System.getProperty("os.name");
        String archName = System.getProperty("os.arch");
        if (!StringUtils.isEmpty(osName) && !StringUtils.isEmpty(archName)) {
            return osName.startsWith("Windows") && archName.indexOf("64") != -1;
        }
        return false;
    }

    /**
     * Forces an outstanding messages to be processed
     */
    public void runMessagesLoop() {
        try {
            while (Display.getCurrent().readAndDispatch()) {
                // wait
            }
        } catch (Throwable e) {
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // IBrowserShellHost
    //
    ////////////////////////////////////////////////////////////////////////////
    public Object createModuleSpaceHost(String moduleName) throws Exception {
        ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(getDevClassLoader());
        try {
            initializePersistentUnitCache();
            // create ShellModuleSpaceHost
            moduleSpaceHost = ReflectionUtils.invokeMethod(impl,
                    "createModuleSpaceHost(java.lang.String,java.io.File,java.lang.String)", moduleName, null,
                    getUserAgent());
            return moduleSpaceHost;
        } finally {
            Thread.currentThread().setContextClassLoader(oldContextClassLoader);
        }
    }

    private void initializePersistentUnitCache() throws Exception {
        // there are failures in tests, don't know why
        /*if (EnvironmentUtils.isTestingTime()) {
          return;
        }*/
        // do initialize
        /*try {
          File cacheDir = new File(SystemUtils.getJavaIoTmpDir(), SystemUtils.USER_NAME + "-gwtd");
          cacheDir.mkdirs();
          ClassLoader devClassLoader = getDevClassLoader();
          Class<?> builderClass =
              devClassLoader.loadClass("com.google.gwt.dev.javac.CompilationStateBuilder");
          ReflectionUtils.invokeMethod(
              builderClass,
              "init(com.google.gwt.core.ext.TreeLogger,java.io.File)",
              logSupport.getLogger(),
              cacheDir);
        } catch (Throwable e) {
        }*/
    }

    /**
     * @return the actual user agent, or "safari" if "warm up" mode.
     */
    private String getUserAgent() {
        if (browserShell == null) {
            return "safari";
        }
        return browserShell.getUserAgentString();
    }

    public Object createModuleSpace(String moduleName, Object msHost, ModuleSpace delegateModuleSpace)
            throws Exception {
        return ReflectionUtils.invokeMethod(impl,
                "createDelegatingModuleSpace(java.lang.Object,java.lang.String,java.lang.Object)", msHost,
                moduleName, delegateModuleSpace);
    }

    public DispatchIdOracle getDispatchIdOracle(Object delegate) throws Exception {
        if (dispatchIdOracle == null) {
            final Object dispatchIdOracleImpl = ReflectionUtils.invokeMethod2(delegate, "getDispatchIdOracle");
            dispatchIdOracle = new DispatchIdOracleImpl(dispatchIdOracleImpl);
        }
        return dispatchIdOracle;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Utils
    //
    ////////////////////////////////////////////////////////////////////////////
    public static String getTemporaryDirectoryName(IJavaProject javaProject) {
        String logDir = javaProject.getProject().getLocation().toOSString() + File.separator + ".gwt";
        File logDirFile = new File(logDir);
        logDirFile.mkdirs();
        return logDir;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // IHostedModeSupport, invocations of native code.
    //
    ////////////////////////////////////////////////////////////////////////////
    @SuppressWarnings("rawtypes")
    public boolean invokeNativeBoolean(String string, Class[] classes, Object[] objects) {
        try {
            return browserShell.getModuleSpace().invokeNativeBoolean(string, null, classes, objects);
        } catch (Throwable e) {
            throw ReflectionUtils.propagate(e);
        }
    }

    @SuppressWarnings("rawtypes")
    public String invokeNativeString(String string, Class[] classes, Object[] objects) {
        try {
            return (String) browserShell.getModuleSpace().invokeNativeObject(string, null, classes, objects);
        } catch (Throwable e) {
            throw ReflectionUtils.propagate(e);
        }
    }

    @SuppressWarnings("rawtypes")
    public void invokeNativeVoid(String string, Class[] classes, Object[] objects) {
        try {
            browserShell.getModuleSpace().invokeNativeVoid(string, null, classes, objects);
        } catch (Throwable e) {
            throw ReflectionUtils.propagate(e);
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Inner classes
    //
    ////////////////////////////////////////////////////////////////////////////
    private static final class DispatchIdOracleImpl implements DispatchIdOracle {
        private final Object dispatchIdOracleImpl;

        private DispatchIdOracleImpl(Object dispatchIdOracleImpl) {
            this.dispatchIdOracleImpl = dispatchIdOracleImpl;
        }

        public int getDispId(String member) {
            try {
                return (Integer) ReflectionUtils.invokeMethod(dispatchIdOracleImpl, "getDispId(java.lang.String)",
                        member);
            } catch (Throwable e) {
                throw ReflectionUtils.propagate(e);
            }
        }

        public DispatchClassInfo getClassInfoByDispId(int dispId) {
            try {
                final Object dispatchClassInfo = ReflectionUtils.invokeMethod(dispatchIdOracleImpl,
                        "getClassInfoByDispId(int)", dispId);
                return new DispatchClassInfo() {
                    public Member getMember(int dispId) {
                        try {
                            return (Member) ReflectionUtils.invokeMethod(dispatchClassInfo, "getMember(int)",
                                    dispId);
                        } catch (Throwable e) {
                            throw ReflectionUtils.propagate(e);
                        }
                    }
                };
            } catch (Throwable e) {
                throw ReflectionUtils.propagate(e);
            }
        }
    }
}