2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.editor.mimelookup.impl;
44
import java.beans.PropertyChangeEvent;
45
import java.beans.PropertyChangeListener;
46
import java.lang.reflect.Method;
47
import java.util.Collections;
48
import java.util.HashMap;
49
import java.util.HashSet;
50
import java.util.List;
53
import java.util.logging.Level;
54
import java.util.logging.Logger;
55
import org.netbeans.api.editor.mimelookup.MimePath;
56
import org.netbeans.spi.editor.mimelookup.InstanceProvider;
57
import org.openide.util.Lookup;
58
import org.openide.util.NbCollections;
59
import org.openide.util.WeakListeners;
60
import org.openide.util.lookup.ProxyLookup;
66
public class SwitchLookup extends Lookup {
68
private static final Logger LOG = Logger.getLogger(SwitchLookup.class.getName());
70
/* package */ static final String ROOT_FOLDER = "Editors"; //NOI18N
72
private MimePath mimePath;
74
private final String LOCK = new String("SwitchLookup.LOCK"); //NOI18N
76
private MappingListener listener;
78
private Map<String,UpdatableProxyLookup> classLookups = new HashMap<String,UpdatableProxyLookup>();
79
private Map<List<String>,Lookup> pathsLookups = new HashMap<List<String>,Lookup>();
81
private Map<String,ClassInfoStorage.Info> classInfos = new HashMap<String,ClassInfoStorage.Info>();
82
private Map<List<String>,Set<String>> pathsToClasses = new HashMap<List<String>,Set<String>>();
84
public SwitchLookup(MimePath mimePath) {
87
this.mimePath = mimePath;
89
this.listener = new MappingListener();
90
ClassInfoStorage.getInstance().addPropertyChangeListener(
91
WeakListeners.propertyChange(listener, ClassInfoStorage.getInstance()));
94
public <T> Lookup.Result<T> lookup(Lookup.Template<T> template) {
95
return findLookup(template.getType()).lookup(template);
98
public <T> T lookup(Class<T> clazz) {
99
return findLookup(clazz).lookup(clazz);
102
private Lookup findLookup(Class clazz) {
103
synchronized (LOCK) {
104
String className = clazz.getName();
105
UpdatableProxyLookup lookup = classLookups.get(className);
106
if (lookup == null) {
107
// Get the the class info and remember it
108
ClassInfoStorage.Info classInfo = ClassInfoStorage.getInstance().getInfo(className);
109
classInfos.put(className, classInfo);
112
Lookup innerLookup = createLookup(classInfo);
113
lookup = new UpdatableProxyLookup(innerLookup);
115
classLookups.put(className, lookup);
122
private Lookup createLookup(ClassInfoStorage.Info classInfo) {
123
List<String> paths = computePaths(mimePath, ROOT_FOLDER, classInfo.getExtraPath());
126
if (classInfo.getInstanceProviderClass() != null) {
127
// Get a lookup for the new instance provider
128
lookup = getLookupForProvider(paths, classInfo.getInstanceProvider());
130
// Add the className to the list of users of the new paths
131
Set<String> pathsUsers = pathsToClasses.get(paths);
132
if (pathsUsers == null) {
133
pathsUsers = new HashSet<String>();
134
pathsToClasses.put(paths, pathsUsers);
136
pathsUsers.add(classInfo.getClassName());
138
// Get a lookup for the new paths
139
lookup = getLookupForPaths(paths);
145
private Lookup getLookupForPaths(List<String> paths) {
146
Lookup lookup = pathsLookups.get(paths);
147
if (lookup == null) {
148
lookup = new FolderPathLookup(paths.toArray(new String[paths.size()]));
149
pathsLookups.put(paths, lookup);
155
private Lookup getLookupForProvider(List<String> paths, InstanceProvider instanceProvider) {
156
return new InstanceProviderLookup(paths.toArray(new String[paths.size()]), instanceProvider);
159
private void rebuildLookup(String className) {
160
synchronized (LOCK) {
161
UpdatableProxyLookup classLookup = classLookups.get(className);
162
if (classLookup == null) {
163
// no lookup for the class, nothing to do
167
ClassInfoStorage.Info currentClassInfo = classInfos.get(className);
168
ClassInfoStorage.Info classInfo = ClassInfoStorage.getInstance().getInfo(className);
170
if (currentClassInfo.equals(classInfo)) {
171
// bogus change event, the class information hasn't changed, nothing to do
175
if (currentClassInfo.getInstanceProviderClass() == null) {
176
List<String> currentPaths = computePaths(mimePath, ROOT_FOLDER, currentClassInfo.getExtraPath());
178
// Remove the className from the list of users of the current paths
179
Set<String> currentPathsUsers = pathsToClasses.get(currentPaths);
180
currentPathsUsers.remove(className);
182
if (currentPathsUsers.isEmpty()) {
183
pathsToClasses.remove(currentPaths);
184
pathsLookups.remove(currentPaths);
188
// Remember the new class info
189
classInfos.put(className, classInfo);
191
// Update the classLookup
192
Lookup innerLookup = createLookup(classInfo);
193
classLookup.setLookupsEx(innerLookup);
197
private static List<String> computePaths(MimePath mimePath, String prefixPath, String suffixPath) {
199
Method m = MimePath.class.getDeclaredMethod("getInheritedPaths", String.class, String.class); //NOI18N
200
m.setAccessible(true);
201
@SuppressWarnings("unchecked")
202
List<String> paths = (List<String>) m.invoke(mimePath, prefixPath, suffixPath);
204
} catch (Exception e) {
205
LOG.log(Level.WARNING, "Can't call org.netbeans.api.editor.mimelookup.MimePath.getInheritedPaths method.", e); //NOI18N
208
// No inherited mimepaths, provide at least something
209
StringBuilder sb = new StringBuilder();
210
if (prefixPath != null && prefixPath.length() > 0) {
211
sb.append(prefixPath);
213
if (mimePath.size() > 0) {
214
if (sb.length() > 0) {
215
sb.append('/'); //NOI18N
217
sb.append(mimePath.getPath());
219
if (suffixPath != null && suffixPath.length() > 0) {
220
if (sb.length() > 0) {
221
sb.append('/'); //NOI18N
223
sb.append(suffixPath);
225
return Collections.singletonList(sb.toString());
229
* An ordinary <code>ProxyLookup</code> except that it exposes the
230
* <code>setLookupEx</code> method.
232
private static final class UpdatableProxyLookup extends ProxyLookup {
233
public UpdatableProxyLookup() {
237
public UpdatableProxyLookup(Lookup... lookups) {
241
public void setLookupsEx(Lookup... lookups) {
244
} // End of UpdatableProxyLookup class
246
private final class MappingListener implements PropertyChangeListener {
247
public void propertyChange(PropertyChangeEvent evt) {
248
for (String className : NbCollections.checkedSetByFilter((Set) evt.getNewValue(), String.class, true)) {
249
rebuildLookup(className);
252
} // End of MappingListsner class