1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.editors.layout.gre; 18 19 import com.android.ide.common.api.IViewRule; 20 import com.android.ide.eclipse.adt.AdtPlugin; 21 import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 22 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 23 import com.android.sdklib.internal.project.ProjectProperties; 24 import com.android.util.Pair; 25 26 import org.eclipse.core.resources.IProject; 27 import org.eclipse.core.runtime.CoreException; 28 import org.eclipse.core.runtime.IStatus; 29 import org.eclipse.core.runtime.QualifiedName; 30 31 import java.io.File; 32 import java.net.MalformedURLException; 33 import java.net.URL; 34 import java.net.URLClassLoader; 35 import java.util.ArrayList; 36 import java.util.List; 37 38 /** 39 * The {@link RuleLoader} is responsible for loading (and unloading) 40 * {@link IViewRule} classes. There is typically one {@link RuleLoader} 41 * per project. 42 */ 43 public class RuleLoader { 44 /** 45 * Qualified name for the per-project non-persistent property storing the 46 * {@link RuleLoader} for this project 47 */ 48 private final static QualifiedName RULE_LOADER = new QualifiedName(AdtPlugin.PLUGIN_ID, 49 "ruleloader"); //$NON-NLS-1$ 50 51 private final IProject mProject; 52 private ClassLoader mUserClassLoader; 53 private List<Pair<File, Long>> mUserJarTimeStamps; 54 private long mLastCheckTimeStamp; 55 56 /** 57 * Flag set when we've attempted to initialize the {@link #mUserClassLoader} 58 * already 59 */ 60 private boolean mUserClassLoaderInited; 61 62 /** 63 * Returns the {@link RuleLoader} for the given project 64 * 65 * @param project the project the loader is associated with 66 * @return an {@RuleLoader} for the given project, 67 * never null 68 */ 69 public static RuleLoader get(IProject project) { 70 RuleLoader loader = null; 71 try { 72 loader = (RuleLoader) project.getSessionProperty(RULE_LOADER); 73 } catch (CoreException e) { 74 // Not a problem; we will just create a new one 75 } 76 if (loader == null) { 77 loader = new RuleLoader(project); 78 try { 79 project.setSessionProperty(RULE_LOADER, loader); 80 } catch (CoreException e) { 81 AdtPlugin.log(e, "Can't store RuleLoader"); 82 } 83 } 84 return loader; 85 } 86 87 /** Do not call; use the {@link #get} factory method instead. */ 88 private RuleLoader(IProject project) { 89 mProject = project; 90 } 91 92 /** 93 * Find out whether the given project has 3rd party ViewRules, and if so 94 * return a ClassLoader which can locate them. If not, return null. 95 * @param project The project to load user rules from 96 * @return A class loader which can user view rules, or otherwise null 97 */ 98 private ClassLoader computeUserClassLoader(IProject project) { 99 // Default place to locate layout rules. The user may also add to this 100 // path by defining a config property specifying 101 // additional .jar files to search via a the layoutrules.jars property. 102 ProjectState state = Sdk.getProjectState(project); 103 ProjectProperties projectProperties = state.getProperties(); 104 105 // Ensure we have the latest & greatest version of the properties. 106 // This allows users to reopen editors in a running Eclipse instance 107 // to get updated view rule jars 108 projectProperties.reload(); 109 110 String path = projectProperties.getProperty( 111 ProjectProperties.PROPERTY_RULES_PATH); 112 113 if (path != null && path.length() > 0) { 114 115 mUserJarTimeStamps = new ArrayList<Pair<File, Long>>(); 116 mLastCheckTimeStamp = System.currentTimeMillis(); 117 118 List<URL> urls = new ArrayList<URL>(); 119 String[] pathElements = path.split(File.pathSeparator); 120 for (String pathElement : pathElements) { 121 pathElement = pathElement.trim(); // Avoid problems with trailing whitespace etc 122 File pathFile = new File(pathElement); 123 if (!pathFile.isAbsolute()) { 124 pathFile = new File(project.getLocation().toFile(), pathElement); 125 } 126 // Directories and jar files are okay. Do we need to 127 // validate the files here as .jar files? 128 if (pathFile.isFile() || pathFile.isDirectory()) { 129 URL url; 130 try { 131 url = pathFile.toURI().toURL(); 132 urls.add(url); 133 134 mUserJarTimeStamps.add(Pair.of(pathFile, pathFile.lastModified())); 135 } catch (MalformedURLException e) { 136 AdtPlugin.log(IStatus.WARNING, 137 "Invalid URL: %1$s", //$NON-NLS-1$ 138 e.toString()); 139 } 140 } 141 } 142 143 if (urls.size() > 0) { 144 return new URLClassLoader(urls.toArray(new URL[urls.size()]), 145 RulesEngine.class.getClassLoader()); 146 } 147 } 148 149 return null; 150 } 151 152 /** 153 * Return the class loader to use for custom views, or null if no custom 154 * view rules are registered for the project. Note that this class loader 155 * can change over time (if the jar files are updated), so callers should be 156 * prepared to unload previous instances. 157 * 158 * @return a class loader to use for custom view rules, or null 159 */ 160 public ClassLoader getClassLoader() { 161 if (mUserClassLoader == null) { 162 // Only attempt to load rule paths once. 163 // TODO: Check the timestamp on the project.properties file so we can dynamically 164 // pick up cases where the user edits the path 165 if (!mUserClassLoaderInited) { 166 mUserClassLoaderInited = true; 167 mUserClassLoader = computeUserClassLoader(mProject); 168 } 169 } else { 170 // Check the timestamp on the jar files in the custom view path to see if we 171 // need to reload the classes (but only do this at most every 3 seconds) 172 if (mUserJarTimeStamps != null) { 173 long time = System.currentTimeMillis(); 174 if (time - mLastCheckTimeStamp > 3000) { 175 mLastCheckTimeStamp = time; 176 for (Pair<File, Long> pair : mUserJarTimeStamps) { 177 File file = pair.getFirst(); 178 Long prevModified = pair.getSecond(); 179 long modified = file.lastModified(); 180 if (prevModified.longValue() != modified) { 181 mUserClassLoaderInited = true; 182 mUserJarTimeStamps = null; 183 mUserClassLoader = computeUserClassLoader(mProject); 184 } 185 } 186 } 187 } 188 } 189 190 return mUserClassLoader; 191 } 192 } 193