/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php * * 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.android.ide.eclipse.adt.internal.editors.layout.gre; import com.android.ide.common.api.IViewRule; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.internal.project.ProjectProperties; import com.android.utils.Pair; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.QualifiedName; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; /** * The {@link RuleLoader} is responsible for loading (and unloading) * {@link IViewRule} classes. There is typically one {@link RuleLoader} * per project. */ public class RuleLoader { /** * Qualified name for the per-project non-persistent property storing the * {@link RuleLoader} for this project */ private final static QualifiedName RULE_LOADER = new QualifiedName(AdtPlugin.PLUGIN_ID, "ruleloader"); //$NON-NLS-1$ private final IProject mProject; private ClassLoader mUserClassLoader; private List<Pair<File, Long>> mUserJarTimeStamps; private long mLastCheckTimeStamp; /** * Flag set when we've attempted to initialize the {@link #mUserClassLoader} * already */ private boolean mUserClassLoaderInited; /** * Returns the {@link RuleLoader} for the given project * * @param project the project the loader is associated with * @return an {@RuleLoader} for the given project, * never null */ public static RuleLoader get(IProject project) { RuleLoader loader = null; try { loader = (RuleLoader) project.getSessionProperty(RULE_LOADER); } catch (CoreException e) { // Not a problem; we will just create a new one } if (loader == null) { loader = new RuleLoader(project); try { project.setSessionProperty(RULE_LOADER, loader); } catch (CoreException e) { AdtPlugin.log(e, "Can't store RuleLoader"); } } return loader; } /** Do not call; use the {@link #get} factory method instead. */ private RuleLoader(IProject project) { mProject = project; } /** * Find out whether the given project has 3rd party ViewRules, and if so * return a ClassLoader which can locate them. If not, return null. * @param project The project to load user rules from * @return A class loader which can user view rules, or otherwise null */ private ClassLoader computeUserClassLoader(IProject project) { // Default place to locate layout rules. The user may also add to this // path by defining a config property specifying // additional .jar files to search via a the layoutrules.jars property. ProjectState state = Sdk.getProjectState(project); ProjectProperties projectProperties = state.getProperties(); // Ensure we have the latest & greatest version of the properties. // This allows users to reopen editors in a running Eclipse instance // to get updated view rule jars projectProperties.reload(); String path = projectProperties.getProperty( ProjectProperties.PROPERTY_RULES_PATH); if (path != null && path.length() > 0) { mUserJarTimeStamps = new ArrayList<Pair<File, Long>>(); mLastCheckTimeStamp = System.currentTimeMillis(); List<URL> urls = new ArrayList<URL>(); String[] pathElements = path.split(File.pathSeparator); for (String pathElement : pathElements) { pathElement = pathElement.trim(); // Avoid problems with trailing whitespace etc File pathFile = new File(pathElement); if (!pathFile.isAbsolute()) { pathFile = new File(project.getLocation().toFile(), pathElement); } // Directories and jar files are okay. Do we need to // validate the files here as .jar files? if (pathFile.isFile() || pathFile.isDirectory()) { URL url; try { url = pathFile.toURI().toURL(); urls.add(url); mUserJarTimeStamps.add(Pair.of(pathFile, pathFile.lastModified())); } catch (MalformedURLException e) { AdtPlugin.log(IStatus.WARNING, "Invalid URL: %1$s", //$NON-NLS-1$ e.toString()); } } } if (urls.size() > 0) { return new URLClassLoader(urls.toArray(new URL[urls.size()]), RulesEngine.class.getClassLoader()); } } return null; } /** * Return the class loader to use for custom views, or null if no custom * view rules are registered for the project. Note that this class loader * can change over time (if the jar files are updated), so callers should be * prepared to unload previous instances. * * @return a class loader to use for custom view rules, or null */ public ClassLoader getClassLoader() { if (mUserClassLoader == null) { // Only attempt to load rule paths once. // TODO: Check the timestamp on the project.properties file so we can dynamically // pick up cases where the user edits the path if (!mUserClassLoaderInited) { mUserClassLoaderInited = true; mUserClassLoader = computeUserClassLoader(mProject); } } else { // Check the timestamp on the jar files in the custom view path to see if we // need to reload the classes (but only do this at most every 3 seconds) if (mUserJarTimeStamps != null) { long time = System.currentTimeMillis(); if (time - mLastCheckTimeStamp > 3000) { mLastCheckTimeStamp = time; for (Pair<File, Long> pair : mUserJarTimeStamps) { File file = pair.getFirst(); Long prevModified = pair.getSecond(); long modified = file.lastModified(); if (prevModified.longValue() != modified) { mUserClassLoaderInited = true; mUserJarTimeStamps = null; mUserClassLoader = computeUserClassLoader(mProject); } } } } } return mUserClassLoader; } }