Added
Link Here
|
1 |
/* |
2 |
* The contents of this file are subject to the terms of the Common Development |
3 |
* and Distribution License (the License). You may not use this file except in |
4 |
* compliance with the License. |
5 |
* |
6 |
* You can obtain a copy of the License at http://www.netbeans.org/cddl.html |
7 |
* or http://www.netbeans.org/cddl.txt. |
8 |
* |
9 |
* When distributing Covered Code, include this CDDL Header Notice in each file |
10 |
* and include the License file at http://www.netbeans.org/cddl.txt. |
11 |
* If applicable, add the following below the CDDL Header, with the fields |
12 |
* enclosed by brackets [] replaced by your own identifying information: |
13 |
* "Portions Copyrighted [year] [name of copyright owner]" |
14 |
* |
15 |
* The Original Software is NetBeans. The Initial Developer of the Original |
16 |
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun |
17 |
* Microsystems, Inc. All Rights Reserved. |
18 |
*/ |
19 |
|
20 |
package org.netbeans.modules.project.ant; |
21 |
|
22 |
import java.beans.PropertyChangeEvent; |
23 |
import java.beans.PropertyChangeListener; |
24 |
import java.beans.PropertyChangeSupport; |
25 |
import java.io.File; |
26 |
import java.io.FileInputStream; |
27 |
import java.io.IOException; |
28 |
import java.io.InputStream; |
29 |
import java.io.OutputStream; |
30 |
import java.lang.ref.Reference; |
31 |
import java.lang.ref.WeakReference; |
32 |
import java.net.MalformedURLException; |
33 |
import java.net.URISyntaxException; |
34 |
import java.net.URL; |
35 |
import java.util.ArrayList; |
36 |
import java.util.Arrays; |
37 |
import java.util.Collections; |
38 |
import java.util.HashMap; |
39 |
import java.util.HashSet; |
40 |
import java.util.List; |
41 |
import java.util.Map; |
42 |
import java.util.Properties; |
43 |
import java.util.Set; |
44 |
import java.util.logging.Logger; |
45 |
import java.util.regex.Matcher; |
46 |
import java.util.regex.Pattern; |
47 |
import javax.swing.JFileChooser; |
48 |
import javax.swing.event.ChangeListener; |
49 |
import javax.swing.filechooser.FileFilter; |
50 |
import org.netbeans.api.project.Project; |
51 |
import org.netbeans.api.project.ProjectManager; |
52 |
import org.netbeans.api.project.libraries.Library; |
53 |
import org.netbeans.api.project.libraries.LibraryManager; |
54 |
import org.netbeans.api.project.ui.OpenProjects; |
55 |
import org.netbeans.api.queries.SharabilityQuery; |
56 |
import org.netbeans.spi.project.AuxiliaryConfiguration; |
57 |
import org.netbeans.spi.project.libraries.ArealLibraryProvider; |
58 |
import org.netbeans.spi.project.libraries.LibraryImplementation; |
59 |
import org.netbeans.spi.project.libraries.LibraryProvider; |
60 |
import org.netbeans.spi.project.libraries.LibraryStorageArea; |
61 |
import org.netbeans.spi.project.libraries.support.LibrariesSupport; |
62 |
import org.netbeans.spi.project.support.ant.AntProjectEvent; |
63 |
import org.netbeans.spi.project.support.ant.AntProjectHelper; |
64 |
import org.netbeans.spi.project.support.ant.AntProjectListener; |
65 |
import org.netbeans.spi.project.support.ant.EditableProperties; |
66 |
import org.netbeans.spi.project.support.ant.PropertyProvider; |
67 |
import org.netbeans.spi.project.support.ant.PropertyUtils; |
68 |
import org.netbeans.spi.queries.SharabilityQueryImplementation; |
69 |
import org.openide.filesystems.FileObject; |
70 |
import org.openide.filesystems.FileUtil; |
71 |
import org.openide.filesystems.URLMapper; |
72 |
import org.openide.util.ChangeSupport; |
73 |
import org.openide.util.Exceptions; |
74 |
import org.openide.util.Mutex; |
75 |
import org.openide.util.MutexException; |
76 |
import org.openide.util.NbBundle; |
77 |
import org.openide.util.NbCollections; |
78 |
import org.openide.util.RequestProcessor; |
79 |
import org.openide.util.Utilities; |
80 |
import org.openide.util.WeakListeners; |
81 |
import org.openide.xml.XMLUtil; |
82 |
import org.w3c.dom.Document; |
83 |
import org.w3c.dom.Element; |
84 |
|
85 |
/** |
86 |
* Supplier of libraries declared in open projects. |
87 |
* @see "issue #44035" |
88 |
*/ |
89 |
public class ProjectLibraryProvider implements ArealLibraryProvider<ProjectLibraryProvider.ProjectLibraryArea,ProjectLibraryProvider.ProjectLibraryImplementation>, PropertyChangeListener, AntProjectListener { |
90 |
|
91 |
private static final String NAMESPACE = "http://www.netbeans.org/ns/ant-project-libraries/1"; // NOI18N |
92 |
private static final String EL_LIBRARIES = "libraries"; // NOI18N |
93 |
private static final String EL_DEFINITIONS = "definitions"; // NOI18N |
94 |
|
95 |
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); |
96 |
private AntProjectListener apl; |
97 |
|
98 |
public static ProjectLibraryProvider INSTANCE; |
99 |
/** |
100 |
* Default constructor for lookup. |
101 |
*/ |
102 |
public ProjectLibraryProvider() { |
103 |
INSTANCE = this; |
104 |
} |
105 |
|
106 |
public Class<ProjectLibraryArea> areaType() { |
107 |
return ProjectLibraryArea.class; |
108 |
} |
109 |
|
110 |
public Class<ProjectLibraryImplementation> libraryType() { |
111 |
return ProjectLibraryImplementation.class; |
112 |
} |
113 |
|
114 |
@Override |
115 |
public String toString() { |
116 |
return "ProjectLibraryProvider"; // NOI18N |
117 |
} |
118 |
|
119 |
// ---- management of areas ---- |
120 |
|
121 |
public void addPropertyChangeListener(PropertyChangeListener listener) { |
122 |
pcs.addPropertyChangeListener(listener); |
123 |
} |
124 |
|
125 |
public void removePropertyChangeListener(PropertyChangeListener listener) { |
126 |
pcs.removePropertyChangeListener(listener); |
127 |
} |
128 |
|
129 |
public Set<ProjectLibraryArea> getOpenAreas() { |
130 |
synchronized (this) { // lazy init of OpenProjects-related stuff is better for unit testing |
131 |
if (apl == null) { |
132 |
apl = WeakListeners.create(AntProjectListener.class, this, null); |
133 |
OpenProjects.getDefault().addPropertyChangeListener(WeakListeners.propertyChange(this, OpenProjects.getDefault())); |
134 |
} |
135 |
} |
136 |
Set<ProjectLibraryArea> areas = new HashSet<ProjectLibraryArea>(); |
137 |
for (Project p : OpenProjects.getDefault().getOpenProjects()) { |
138 |
AntProjectHelper helper = AntBasedProjectFactorySingleton.getHelperFor(p); |
139 |
if (helper == null) { |
140 |
// Not an Ant-based project; ignore. |
141 |
continue; |
142 |
} |
143 |
helper.removeAntProjectListener(apl); |
144 |
helper.addAntProjectListener(apl); |
145 |
Definitions def = findDefinitions(helper); |
146 |
if (def != null) { |
147 |
areas.add(new ProjectLibraryArea(def.mainPropertiesFile)); |
148 |
} |
149 |
} |
150 |
return areas; |
151 |
} |
152 |
|
153 |
public ProjectLibraryArea createArea() { |
154 |
JFileChooser jfc = new JFileChooser(); |
155 |
jfc.setApproveButtonText(NbBundle.getMessage(ProjectLibraryProvider.class, "ProjectLibraryProvider.open_or_create")); |
156 |
FileFilter filter = new FileFilter() { |
157 |
public boolean accept(File f) { |
158 |
return f.isDirectory() || (f.getName().endsWith(".properties") && !f.getName().endsWith("-private.properties")); // NOI18N |
159 |
} |
160 |
public String getDescription() { |
161 |
return NbBundle.getMessage(ProjectLibraryProvider.class, "ProjectLibraryProvider.properties_files"); |
162 |
} |
163 |
}; |
164 |
jfc.setFileFilter(filter); |
165 |
FileUtil.preventFileChooserSymlinkTraversal(jfc, null); // XXX remember last-selected dir |
166 |
while (jfc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { |
167 |
File f = jfc.getSelectedFile(); |
168 |
if (filter.accept(f)) { |
169 |
return new ProjectLibraryArea(f); |
170 |
} |
171 |
// Else bad filename, reopen dialog. XXX would be better to just disable OK button, but not sure how...? |
172 |
} |
173 |
return null; |
174 |
} |
175 |
|
176 |
public ProjectLibraryArea loadArea(URL location) { |
177 |
if (location.getProtocol().equals("file") && location.getPath().endsWith(".properties")) { // NOI18N |
178 |
try { |
179 |
return new ProjectLibraryArea(new File(location.toURI())); |
180 |
} catch (URISyntaxException x) { |
181 |
Exceptions.printStackTrace(x); |
182 |
} |
183 |
} |
184 |
return null; |
185 |
} |
186 |
|
187 |
public void propertyChange(PropertyChangeEvent ev) { |
188 |
if (OpenProjects.PROPERTY_OPEN_PROJECTS.equals(ev.getPropertyName())) { |
189 |
pcs.firePropertyChange(ArealLibraryProvider.PROP_OPEN_AREAS, null, null); |
190 |
} |
191 |
} |
192 |
|
193 |
public void configurationXmlChanged(AntProjectEvent ev) { |
194 |
pcs.firePropertyChange(ArealLibraryProvider.PROP_OPEN_AREAS, null, null); |
195 |
} |
196 |
|
197 |
public void propertiesChanged(AntProjectEvent ev) {} |
198 |
|
199 |
// ---- management of libraries ---- |
200 |
|
201 |
private boolean listening = true; |
202 |
private final Map<ProjectLibraryArea,Reference<LP>> providers = new HashMap<ProjectLibraryArea,Reference<LP>>(); |
203 |
|
204 |
private final class LP implements LibraryProvider<ProjectLibraryImplementation>, FileChangeSupportListener { |
205 |
|
206 |
private final ProjectLibraryArea area; |
207 |
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); |
208 |
private final Map<String,ProjectLibraryImplementation> libraries; |
209 |
|
210 |
LP(ProjectLibraryArea area) { |
211 |
this.area = area; |
212 |
libraries = calculate(area); |
213 |
Definitions defs = new Definitions(area.mainPropertiesFile); |
214 |
FileChangeSupport.DEFAULT.addListener(this, defs.mainPropertiesFile); |
215 |
FileChangeSupport.DEFAULT.addListener(this, defs.privatePropertiesFile); |
216 |
} |
217 |
|
218 |
public synchronized ProjectLibraryImplementation[] getLibraries() { |
219 |
return libraries.values().toArray(new ProjectLibraryImplementation[libraries.size()]); |
220 |
} |
221 |
|
222 |
ProjectLibraryImplementation getLibrary(String name) { |
223 |
return libraries.get(name); |
224 |
} |
225 |
|
226 |
public void addPropertyChangeListener(PropertyChangeListener listener) { |
227 |
pcs.addPropertyChangeListener(listener); |
228 |
} |
229 |
|
230 |
public void removePropertyChangeListener(PropertyChangeListener listener) { |
231 |
pcs.removePropertyChangeListener(listener); |
232 |
} |
233 |
|
234 |
public void fileCreated(FileChangeSupportEvent event) { |
235 |
recalculate(); |
236 |
} |
237 |
|
238 |
public void fileDeleted(FileChangeSupportEvent event) { |
239 |
recalculate(); |
240 |
} |
241 |
|
242 |
public void fileModified(FileChangeSupportEvent event) { |
243 |
recalculate(); |
244 |
} |
245 |
|
246 |
private synchronized void recalculate() { |
247 |
if (delta(libraries, calculate(area))) { |
248 |
pcs.firePropertyChange(LibraryProvider.PROP_LIBRARIES, null, null); |
249 |
} |
250 |
} |
251 |
|
252 |
} |
253 |
|
254 |
public synchronized LP getLibraries(ProjectLibraryArea area) { |
255 |
Reference<LP> rlp = providers.get(area); |
256 |
LP lp = rlp != null ? rlp.get() : null; |
257 |
if (lp == null) { |
258 |
lp = new LP(area); |
259 |
providers.put(area, new WeakReference<LP>(lp)); |
260 |
} |
261 |
return lp; |
262 |
} |
263 |
|
264 |
public ProjectLibraryImplementation createLibrary(String type, String name, ProjectLibraryArea area, Map<String,List<URL>> contents) throws IOException { |
265 |
File f = area.mainPropertiesFile; |
266 |
assert listening; |
267 |
listening = false; |
268 |
try { |
269 |
if (type.equals("j2se")) { // NOI18N |
270 |
replaceProperty(f, true, "libs." + name + ".classpath", ""); // NOI18N |
271 |
} else { |
272 |
replaceProperty(f, false, "libs." + name + ".type", type); // NOI18N |
273 |
} |
274 |
} finally { |
275 |
listening = true; |
276 |
} |
277 |
LP lp = getLibraries(area); |
278 |
lp.recalculate(); |
279 |
ProjectLibraryImplementation impl = lp.getLibrary(name); |
280 |
assert impl != null : name + " not found in " + f; |
281 |
for (Map.Entry<String,List<URL>> entry : contents.entrySet()) { |
282 |
impl.setContent(entry.getKey(), entry.getValue()); |
283 |
} |
284 |
return impl; |
285 |
} |
286 |
|
287 |
public void remove(ProjectLibraryImplementation pli) throws IOException { |
288 |
String prefix = "libs." + pli.name + "."; // NOI18N |
289 |
// XXX run atomically to fire changes just once: |
290 |
for (File f : new File[] {pli.mainPropertiesFile, pli.privatePropertiesFile}) { |
291 |
for (String k : loadProperties(f).keySet()) { |
292 |
if (k.startsWith(prefix)) { |
293 |
replaceProperty(f, false, k); |
294 |
} |
295 |
} |
296 |
} |
297 |
} |
298 |
|
299 |
/** one definitions entry */ |
300 |
private static final class Definitions { |
301 |
/** may or may not exist; in case you need to listen to it */ |
302 |
final File mainPropertiesFile; |
303 |
/** similar to {@link #mainPropertiesFile} but for *-private.properties; null if main is not *.properties */ |
304 |
final File privatePropertiesFile; |
305 |
private Map<String,String> properties; |
306 |
Definitions(File mainPropertiesFile) { |
307 |
this.mainPropertiesFile = mainPropertiesFile; |
308 |
String suffix = ".properties"; // NOI18N |
309 |
String name = mainPropertiesFile.getName(); |
310 |
if (name.endsWith(suffix)) { |
311 |
privatePropertiesFile = new File(mainPropertiesFile.getParentFile(), name.substring(0, name.length() - suffix.length()) + "-private" + suffix); // NOI18N |
312 |
} else { |
313 |
privatePropertiesFile = null; |
314 |
} |
315 |
} |
316 |
/** with ${base} resolved according to resolveBase; may be empty or have junk defs */ |
317 |
synchronized Map<String,String> properties(boolean resolveBase) { |
318 |
if (properties == null) { |
319 |
properties = new HashMap<String,String>(); |
320 |
String basedir = mainPropertiesFile.getParent(); |
321 |
for (Map.Entry<String,String> entry : loadProperties(mainPropertiesFile).entrySet()) { |
322 |
String value = entry.getValue(); |
323 |
if (resolveBase) { |
324 |
value = value.replace("${base}", basedir); // NOI18N |
325 |
} |
326 |
properties.put(entry.getKey(), value.replace('/', File.separatorChar)); |
327 |
} |
328 |
if (privatePropertiesFile != null) { |
329 |
for (Map.Entry<String,String> entry : loadProperties(privatePropertiesFile).entrySet()) { |
330 |
String value = entry.getValue(); |
331 |
if (resolveBase) { |
332 |
value = value.replace("${base}", basedir); // NOI18N |
333 |
} |
334 |
properties.put(entry.getKey(), value.replace('/', File.separatorChar)); |
335 |
} |
336 |
} |
337 |
} |
338 |
return properties; |
339 |
} |
340 |
} |
341 |
|
342 |
private static Definitions findDefinitions(AntProjectHelper helper) { |
343 |
String text = getLibrariesLocationText(helper.createAuxiliaryConfiguration()); |
344 |
if (text != null) { |
345 |
File mainPropertiesFile = helper.resolveFile(text); |
346 |
if (mainPropertiesFile.getName().endsWith(".properties")) { // NOI18N |
347 |
return new Definitions(mainPropertiesFile); |
348 |
} |
349 |
} |
350 |
return null; |
351 |
} |
352 |
|
353 |
public static File getLibrariesLocation(AuxiliaryConfiguration aux, File projectFolder) { |
354 |
String text = getLibrariesLocationText(aux); |
355 |
if (text != null) { |
356 |
return PropertyUtils.resolveFile(projectFolder, text); |
357 |
} |
358 |
return null; |
359 |
} |
360 |
|
361 |
/** |
362 |
* Returns libraries location as text. |
363 |
*/ |
364 |
public static String getLibrariesLocationText(AuxiliaryConfiguration aux) { |
365 |
Element libraries = aux.getConfigurationFragment(EL_LIBRARIES, NAMESPACE, true); |
366 |
if (libraries != null) { |
367 |
for (Element definitions : Util.findSubElements(libraries)) { |
368 |
assert definitions.getLocalName().equals(EL_DEFINITIONS) : definitions; |
369 |
String text = Util.findText(definitions); |
370 |
assert text != null : aux; |
371 |
return text; |
372 |
} |
373 |
} |
374 |
return null; |
375 |
} |
376 |
|
377 |
private static Map<String,String> loadProperties(File f) { |
378 |
if (!f.isFile()) { |
379 |
return Collections.emptyMap(); |
380 |
} |
381 |
Properties p = new Properties(); |
382 |
try { |
383 |
InputStream is = new FileInputStream(f); |
384 |
try { |
385 |
p.load(is); |
386 |
} finally { |
387 |
is.close(); |
388 |
} |
389 |
return NbCollections.checkedMapByFilter(p, String.class, String.class, true); |
390 |
} catch (IOException x) { |
391 |
Exceptions.attachMessage(x, "Loading: " + f); // NOI18N |
392 |
Exceptions.printStackTrace(x); |
393 |
return Collections.emptyMap(); |
394 |
} |
395 |
} |
396 |
|
397 |
private static final Pattern LIBS_LINE = Pattern.compile("libs\\.([^.]+)\\.([^.]+)"); // NOI18N |
398 |
private static Map<String,ProjectLibraryImplementation> calculate(ProjectLibraryArea area) { |
399 |
Map<String,ProjectLibraryImplementation> libs = new HashMap<String,ProjectLibraryImplementation>(); |
400 |
Definitions def = new Definitions(area.mainPropertiesFile); |
401 |
Map<String,Map<String,String>> data = new HashMap<String,Map<String,String>>(); |
402 |
for (Map.Entry<String,String> entry : def.properties(false).entrySet()) { |
403 |
Matcher match = LIBS_LINE.matcher(entry.getKey()); |
404 |
if (!match.matches()) { |
405 |
continue; |
406 |
} |
407 |
String name = match.group(1); |
408 |
Map<String,String> subdata = data.get(name); |
409 |
if (subdata == null) { |
410 |
subdata = new HashMap<String,String>(); |
411 |
data.put(name, subdata); |
412 |
} |
413 |
subdata.put(match.group(2), entry.getValue()); |
414 |
} |
415 |
for (Map.Entry<String,Map<String,String>> entry : data.entrySet()) { |
416 |
String name = entry.getKey(); |
417 |
String type = "j2se"; // NOI18N |
418 |
String description = null; |
419 |
Map<String,List<URL>> contents = new HashMap<String,List<URL>>(); |
420 |
for (Map.Entry<String,String> subentry : entry.getValue().entrySet()) { |
421 |
String k = subentry.getKey(); |
422 |
if (k.equals("type")) { // NOI18N |
423 |
type = subentry.getValue(); |
424 |
} else if (k.equals("name")) { // NOI18N |
425 |
// XXX currently overriding display name is not supported |
426 |
} else if (k.equals("description")) { // NOI18N |
427 |
description = subentry.getValue(); |
428 |
} else { |
429 |
String[] path = PropertyUtils.tokenizePath(subentry.getValue()); |
430 |
List<URL> volume = new ArrayList<URL>(path.length); |
431 |
for (String component : path) { |
432 |
File f = new File(component.replace('/', File.separatorChar).replace('\\', File.separatorChar).replace("${base}"+File.separatorChar, "")); |
433 |
File normalizedFile = FileUtil.normalizeFile(new File(component.replace('/', File.separatorChar).replace('\\', File.separatorChar).replace("${base}", area.mainPropertiesFile.getParent()))); |
434 |
try { |
435 |
URL u = LibrariesSupport.convertFileToURL(f); |
436 |
if (FileUtil.isArchiveFile(normalizedFile.toURI().toURL())) { |
437 |
u = FileUtil.getArchiveRoot(u); |
438 |
} else if (!u.toExternalForm().endsWith("/")) { |
439 |
u = new URL(u.toExternalForm() + "/"); |
440 |
} |
441 |
volume.add(u); |
442 |
} catch (MalformedURLException x) { |
443 |
Exceptions.printStackTrace(x); |
444 |
} |
445 |
} |
446 |
contents.put(k, volume); |
447 |
} |
448 |
} |
449 |
libs.put(name, new ProjectLibraryImplementation(def.mainPropertiesFile, def.privatePropertiesFile, type, name, description, contents)); |
450 |
} |
451 |
return libs; |
452 |
} |
453 |
|
454 |
private synchronized boolean delta(Map<String,ProjectLibraryImplementation> libraries, Map<String,ProjectLibraryImplementation> newLibraries) { |
455 |
if (!listening) { |
456 |
return false; |
457 |
} |
458 |
Set<String> added = new HashSet<String>(newLibraries.keySet()); |
459 |
added.removeAll(libraries.keySet()); |
460 |
Set<String> removed = new HashSet<String>(); |
461 |
for (Map.Entry<String,ProjectLibraryImplementation> entry : libraries.entrySet()) { |
462 |
String name = entry.getKey(); |
463 |
ProjectLibraryImplementation old = entry.getValue(); |
464 |
ProjectLibraryImplementation nue = newLibraries.get(name); |
465 |
if (nue == null) { |
466 |
removed.add(name); |
467 |
continue; |
468 |
} |
469 |
if (!old.type.equals(nue.type)) { |
470 |
// Cannot fire this. |
471 |
added.add(name); |
472 |
removed.add(name); |
473 |
libraries.put(name, nue); |
474 |
continue; |
475 |
} |
476 |
assert old.name.equals(nue.name); |
477 |
if (!Utilities.compareObjects(old.description, nue.description)) { |
478 |
old.description = nue.description; |
479 |
old.pcs.firePropertyChange(LibraryImplementation.PROP_DESCRIPTION, null, null); |
480 |
} |
481 |
if (!old.contents.equals(nue.contents)) { |
482 |
old.contents = nue.contents; |
483 |
old.pcs.firePropertyChange(LibraryImplementation.PROP_CONTENT, null, null); |
484 |
} |
485 |
} |
486 |
for (String name : added) { |
487 |
libraries.put(name, newLibraries.get(name)); |
488 |
} |
489 |
for (String name : removed) { |
490 |
libraries.remove(name); |
491 |
} |
492 |
return !added.isEmpty() || !removed.isEmpty(); |
493 |
} |
494 |
|
495 |
static final class ProjectLibraryImplementation implements LibraryImplementation { |
496 |
|
497 |
final File mainPropertiesFile, privatePropertiesFile; |
498 |
final String type; |
499 |
String name; |
500 |
String description; |
501 |
Map<String,List<URL>> contents; |
502 |
final PropertyChangeSupport pcs = new PropertyChangeSupport(this); |
503 |
|
504 |
ProjectLibraryImplementation(File mainPropertiesFile, File privatePropertiesFile, String type, String name, String description, Map<String,List<URL>> contents) { |
505 |
this.mainPropertiesFile = mainPropertiesFile; |
506 |
this.privatePropertiesFile = privatePropertiesFile; |
507 |
this.type = type; |
508 |
this.name = name; |
509 |
this.description = description; |
510 |
this.contents = contents; |
511 |
} |
512 |
|
513 |
public String getType() { |
514 |
return type; |
515 |
} |
516 |
|
517 |
public String getName() { |
518 |
return name; |
519 |
} |
520 |
|
521 |
public String getDescription() { |
522 |
return description; |
523 |
} |
524 |
|
525 |
public String getLocalizingBundle() { |
526 |
return null; |
527 |
} |
528 |
|
529 |
public List<URL> getContent(String volumeType) throws IllegalArgumentException { |
530 |
List<URL> content = contents.get(volumeType); |
531 |
if (content == null) { |
532 |
content = Collections.emptyList(); |
533 |
} |
534 |
return content; |
535 |
} |
536 |
|
537 |
public void setName(String name) { |
538 |
this.name = name; |
539 |
pcs.firePropertyChange(LibraryImplementation.PROP_NAME, null, null); |
540 |
throw new UnsupportedOperationException(); // XXX will anyone call this? |
541 |
} |
542 |
|
543 |
public void setDescription(String text) { |
544 |
throw new UnsupportedOperationException(); // XXX will anyone call this? |
545 |
} |
546 |
|
547 |
public void setContent(String volumeType, List<URL> path) throws IllegalArgumentException { |
548 |
if (path.equals(getContent(volumeType))) { |
549 |
return; |
550 |
} |
551 |
List<String> value = new ArrayList<String>(); |
552 |
for (URL entry : path) { |
553 |
if ("jar".equals(entry.getProtocol())) { // NOI18N |
554 |
if (!entry.toExternalForm().endsWith("!/")) { // NOI18N |
555 |
throw new IllegalArgumentException("Non-root folders in JARs not permitted"); // NOI18N |
556 |
} |
557 |
entry = FileUtil.getArchiveFile(entry); |
558 |
} else if (!"file".equals(entry.getProtocol())) { // NOI18N |
559 |
throw new IllegalArgumentException(entry.toExternalForm()); |
560 |
} |
561 |
File f = LibrariesSupport.convertURLToFile(entry); |
562 |
// store properties always separated by '/' for consistency |
563 |
String s; |
564 |
if (f.isAbsolute()) { |
565 |
s = f.getAbsolutePath().replace('\\', '/'); |
566 |
} else { |
567 |
s = "${base}/" + f.getPath().replace('\\', '/'); // NOI18N |
568 |
} |
569 |
if (value.size()+1 != path.size()) { |
570 |
s += File.pathSeparatorChar; |
571 |
} |
572 |
value.add(s); |
573 |
} |
574 |
String key = "libs." + name + "." + volumeType; // NOI18N |
575 |
try { |
576 |
replaceProperty(mainPropertiesFile, true, key, value.toArray(new String[value.size()])); |
577 |
} catch (IOException x) { |
578 |
throw new IllegalArgumentException(x); |
579 |
} |
580 |
} |
581 |
|
582 |
public void setLocalizingBundle(String resourceName) { |
583 |
throw new UnsupportedOperationException(); |
584 |
} |
585 |
|
586 |
public void addPropertyChangeListener(PropertyChangeListener l) { |
587 |
pcs.addPropertyChangeListener(l); |
588 |
} |
589 |
|
590 |
public void removePropertyChangeListener(PropertyChangeListener l) { |
591 |
pcs.removePropertyChangeListener(l); |
592 |
} |
593 |
|
594 |
@Override |
595 |
public String toString() { |
596 |
return "ProjectLibraryImplementation[name=" + name + ",file=" + mainPropertiesFile + ",contents=" + contents + "]"; // NOI18N |
597 |
} |
598 |
|
599 |
} |
600 |
|
601 |
private static void replaceProperty(File propfile, boolean classPathLikeValue, String key, String... value) throws IOException { |
602 |
EditableProperties ep = new EditableProperties(); |
603 |
if (propfile.isFile()) { |
604 |
InputStream is = new FileInputStream(propfile); |
605 |
try { |
606 |
ep.load(is); |
607 |
} finally { |
608 |
is.close(); |
609 |
} |
610 |
} |
611 |
if (Utilities.compareObjects(value, ep.getProperty(key))) { |
612 |
return; |
613 |
} |
614 |
if (value.length > 0) { |
615 |
if (classPathLikeValue) { |
616 |
ep.setProperty(key, value); |
617 |
} else { |
618 |
assert value.length == 1 : Arrays.asList(value); |
619 |
ep.setProperty(key, value[0]); |
620 |
} |
621 |
} else { |
622 |
ep.remove(key); |
623 |
} |
624 |
FileObject fo = FileUtil.createData(propfile); |
625 |
OutputStream os = fo.getOutputStream(); |
626 |
try { |
627 |
ep.store(os); |
628 |
} finally { |
629 |
os.close(); |
630 |
} |
631 |
} |
632 |
|
633 |
static final class ProjectLibraryArea implements LibraryStorageArea { |
634 |
|
635 |
final File mainPropertiesFile; |
636 |
|
637 |
ProjectLibraryArea(File mainPropertiesFile) { |
638 |
assert mainPropertiesFile.getName().endsWith(".properties") : mainPropertiesFile; |
639 |
this.mainPropertiesFile = mainPropertiesFile; |
640 |
} |
641 |
|
642 |
public String getDisplayName() { |
643 |
return mainPropertiesFile.getAbsolutePath(); |
644 |
} |
645 |
|
646 |
public URL getLocation() { |
647 |
try { |
648 |
return mainPropertiesFile.toURI().toURL(); |
649 |
} catch (MalformedURLException x) { |
650 |
throw new AssertionError(x); |
651 |
} |
652 |
} |
653 |
|
654 |
public boolean equals(Object obj) { |
655 |
return obj instanceof ProjectLibraryArea && ((ProjectLibraryArea) obj).mainPropertiesFile.equals(mainPropertiesFile); |
656 |
} |
657 |
|
658 |
public int hashCode() { |
659 |
return mainPropertiesFile.hashCode(); |
660 |
} |
661 |
|
662 |
@Override |
663 |
public String toString() { |
664 |
return "ProjectLibraryArea[" + mainPropertiesFile + "]"; // NOI18N |
665 |
} |
666 |
|
667 |
} |
668 |
|
669 |
/** |
670 |
* Used from {@link AntProjectHelper#getProjectLibrariesPropertyProvider}. |
671 |
* @param helper a project |
672 |
* @return a provider of project library definition properties |
673 |
*/ |
674 |
public static PropertyProvider createPropertyProvider(final AntProjectHelper helper) { |
675 |
class PP implements PropertyProvider, FileChangeSupportListener, AntProjectListener { |
676 |
final ChangeSupport cs = new ChangeSupport(this); |
677 |
final Set<File> listeningTo = new HashSet<File>(); |
678 |
{ |
679 |
helper.addAntProjectListener(WeakListeners.create(AntProjectListener.class, this, helper)); |
680 |
} |
681 |
private void listenTo(File f, Set<File> noLongerListeningTo) { |
682 |
if (f != null) { |
683 |
noLongerListeningTo.remove(f); |
684 |
if (listeningTo.add(f)) { |
685 |
FileChangeSupport.DEFAULT.addListener(this, f); |
686 |
} |
687 |
} |
688 |
} |
689 |
public synchronized Map<String,String> getProperties() { |
690 |
Map<String,String> m = new HashMap<String,String>(); |
691 |
// XXX add an AntProjectListener |
692 |
Set<File> noLongerListeningTo = new HashSet<File>(listeningTo); |
693 |
Definitions def = findDefinitions(helper); |
694 |
if (def != null) { |
695 |
m.putAll(def.properties(true)); |
696 |
listenTo(def.mainPropertiesFile, noLongerListeningTo); |
697 |
listenTo(def.privatePropertiesFile, noLongerListeningTo); |
698 |
} |
699 |
for (File f : noLongerListeningTo) { |
700 |
listeningTo.remove(f); |
701 |
FileChangeSupport.DEFAULT.removeListener(this, f); |
702 |
} |
703 |
return m; |
704 |
} |
705 |
public void addChangeListener(ChangeListener l) { |
706 |
cs.addChangeListener(l); |
707 |
} |
708 |
public void removeChangeListener(ChangeListener l) { |
709 |
cs.removeChangeListener(l); |
710 |
} |
711 |
public void fileCreated(FileChangeSupportEvent event) { |
712 |
fireChangeNowOrLater(); |
713 |
} |
714 |
public void fileDeleted(FileChangeSupportEvent event) { |
715 |
fireChangeNowOrLater(); |
716 |
} |
717 |
public void fileModified(FileChangeSupportEvent event) { |
718 |
fireChangeNowOrLater(); |
719 |
} |
720 |
void fireChangeNowOrLater() { |
721 |
// See PropertyUtils.FilePropertyProvider. |
722 |
if (!cs.hasListeners()) { |
723 |
return; |
724 |
} |
725 |
final Mutex.Action<Void> action = new Mutex.Action<Void>() { |
726 |
public Void run() { |
727 |
cs.fireChange(); |
728 |
return null; |
729 |
} |
730 |
}; |
731 |
if (ProjectManager.mutex().isWriteAccess() || FIRE_CHANGES_SYNCH) { |
732 |
ProjectManager.mutex().readAccess(action); |
733 |
} else if (ProjectManager.mutex().isReadAccess()) { |
734 |
action.run(); |
735 |
} else { |
736 |
RP.post(new Runnable() { |
737 |
public void run() { |
738 |
ProjectManager.mutex().readAccess(action); |
739 |
} |
740 |
}); |
741 |
} |
742 |
} |
743 |
public void configurationXmlChanged(AntProjectEvent ev) { |
744 |
cs.fireChange(); |
745 |
} |
746 |
public void propertiesChanged(AntProjectEvent ev) {} |
747 |
} |
748 |
return new PP(); |
749 |
} |
750 |
private static final RequestProcessor RP = new RequestProcessor("ProjectLibraryProvider.RP"); // NOI18N |
751 |
public static boolean FIRE_CHANGES_SYNCH = false; // used by tests |
752 |
|
753 |
/** |
754 |
* Is this library reachable from this project? Returns true if given library |
755 |
* is defined in libraries location associated with this project. |
756 |
*/ |
757 |
public static boolean isReachableLibrary(Library library, AntProjectHelper helper) { |
758 |
URL location = library.getManager().getLocation(); |
759 |
if (location == null) { |
760 |
return false; |
761 |
} |
762 |
ProjectLibraryArea area = INSTANCE.loadArea(location); |
763 |
if (area == null) { |
764 |
return false; |
765 |
} |
766 |
ProjectLibraryImplementation pli = INSTANCE.getLibraries(area).getLibrary(library.getName()); |
767 |
if (pli == null) { |
768 |
return false; |
769 |
} |
770 |
Definitions def = findDefinitions(helper); |
771 |
if (def == null) { |
772 |
return false; |
773 |
} |
774 |
return def.mainPropertiesFile.equals(pli.mainPropertiesFile); |
775 |
} |
776 |
|
777 |
/** |
778 |
* Create element for shared libraries to store in project.xml. |
779 |
* |
780 |
* @param doc XML document |
781 |
* @param location project relative or absolute OS path; cannot be null |
782 |
* @return element |
783 |
*/ |
784 |
public static Element createLibrariesElement(Document doc, String location) { |
785 |
Element libraries = doc.createElementNS(NAMESPACE, EL_LIBRARIES); |
786 |
libraries.appendChild(libraries.getOwnerDocument().createElementNS(NAMESPACE, EL_DEFINITIONS)). |
787 |
appendChild(libraries.getOwnerDocument().createTextNode(location)); |
788 |
return libraries; |
789 |
} |
790 |
|
791 |
/** |
792 |
* Used from {@link ReferenceHelper#getProjectLibraryManager}. |
793 |
*/ |
794 |
public static LibraryManager getProjectLibraryManager(AntProjectHelper helper) { |
795 |
Definitions defs = findDefinitions(helper); |
796 |
if (defs != null) { |
797 |
try { |
798 |
return LibraryManager.forLocation(defs.mainPropertiesFile.toURI().toURL()); |
799 |
} catch (MalformedURLException x) { |
800 |
Exceptions.printStackTrace(x); |
801 |
} |
802 |
} |
803 |
return null; |
804 |
} |
805 |
|
806 |
/** |
807 |
* Stores given libraries location in given project. |
808 |
*/ |
809 |
public static void setLibrariesLocation(AntProjectHelper helper, String librariesDefinition) { |
810 |
Element libraries = helper.createAuxiliaryConfiguration().getConfigurationFragment(EL_LIBRARIES, NAMESPACE, true); |
811 |
if (libraries == null) { |
812 |
libraries = XMLUtil.createDocument("dummy", null, null, null).createElementNS(NAMESPACE, EL_LIBRARIES); // NOI18N |
813 |
} else { |
814 |
List<Element> elements = Util.findSubElements(libraries); |
815 |
if (elements.size() == 1) { |
816 |
libraries.removeChild(elements.get(0)); |
817 |
} |
818 |
} |
819 |
libraries.appendChild(libraries.getOwnerDocument().createElementNS(NAMESPACE, EL_DEFINITIONS)). |
820 |
appendChild(libraries.getOwnerDocument().createTextNode(librariesDefinition)); |
821 |
helper.createAuxiliaryConfiguration().putConfigurationFragment(libraries, true); |
822 |
} |
823 |
|
824 |
/** |
825 |
* Used from {@link org.netbeans.spi.project.support.ant.SharabilityQueryImpl}. |
826 |
*/ |
827 |
public static List<String> getUnsharablePathsWithinProject(AntProjectHelper helper) { |
828 |
List<String> paths = new ArrayList<String>(); |
829 |
Definitions defs = findDefinitions(helper); |
830 |
if (defs != null) { |
831 |
if (defs.privatePropertiesFile != null) { |
832 |
paths.add(defs.privatePropertiesFile.getAbsolutePath()); |
833 |
} |
834 |
} |
835 |
return paths; |
836 |
} |
837 |
|
838 |
public static final class SharabilityQueryImpl implements SharabilityQueryImplementation { |
839 |
|
840 |
/** Default constructor for lookup. */ |
841 |
public SharabilityQueryImpl() {} |
842 |
|
843 |
public int getSharability(File file) { |
844 |
if (file.getName().endsWith("-private.properties")) { // NOI18N |
845 |
return SharabilityQuery.NOT_SHARABLE; |
846 |
} else { |
847 |
return SharabilityQuery.UNKNOWN; |
848 |
} |
849 |
} |
850 |
|
851 |
} |
852 |
|
853 |
/** |
854 |
* Used from {@link org.netbeans.spi.project.support.ant.ReferenceHelper}. |
855 |
*/ |
856 |
public static Library copyLibrary(final Library lib, final URL location, |
857 |
final boolean generateLibraryUniqueName) throws IOException { |
858 |
final File libBaseFolder = LibrariesSupport.convertURLToFile(location).getParentFile(); |
859 |
FileObject sharedLibFolder; |
860 |
try { |
861 |
sharedLibFolder = ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<FileObject>() { |
862 |
public FileObject run() throws IOException { |
863 |
FileObject lf = FileUtil.toFileObject(libBaseFolder); |
864 |
return lf.createFolder(getUniqueName(lf, lib.getName(), null)); |
865 |
} |
866 |
}); |
867 |
} catch (MutexException ex) { |
868 |
throw (IOException)ex.getException(); |
869 |
} |
870 |
final Map<String, List<URL>> content = new HashMap<String, List<URL>>(); |
871 |
String[] volumes = LibrariesSupport.getLibraryTypeProvider(lib.getType()).getSupportedVolumeTypes(); |
872 |
for (String volume : volumes) { |
873 |
List<URL> volumeContent = new ArrayList<URL>(); |
874 |
for (URL libEntry : lib.getContent(volume)) { |
875 |
if ("jar".equals(libEntry.getProtocol())) { // NOI18N |
876 |
libEntry = FileUtil.getArchiveFile(libEntry); |
877 |
} |
878 |
FileObject libEntryFO = URLMapper.findFileObject(libEntry); |
879 |
if (libEntryFO == null) { |
880 |
if (!"file".equals(libEntry.getProtocol()) && // NOI18N |
881 |
!"nbinst".equals(libEntry.getProtocol())) { // NOI18N |
882 |
Logger.getLogger(ProjectLibraryProvider.class.getName()).warning("copyLibrary is ignoring entry "+libEntry); |
883 |
continue; |
884 |
} else { |
885 |
Logger.getLogger(ProjectLibraryProvider.class.getName()).warning("Library '"+lib.getDisplayName()+ // NOI18N |
886 |
"' contains entry ("+libEntry+") which does not exist. This entry is ignored and will not be copied to sharable libraries location."); // NOI18N |
887 |
continue; |
888 |
} |
889 |
} |
890 |
FileObject newFO; |
891 |
String name; |
892 |
if (libEntryFO.isFolder()) { |
893 |
newFO = FileChooserAccessory.copyFolderRecursively(libEntryFO, sharedLibFolder); |
894 |
name = sharedLibFolder.getName()+File.separatorChar+newFO.getName()+File.separatorChar; |
895 |
} else { |
896 |
String libEntryName = getUniqueName(sharedLibFolder, libEntryFO.getName(), libEntryFO.getExt()); |
897 |
newFO = FileUtil.copyFile(libEntryFO, sharedLibFolder, libEntryName); |
898 |
name = sharedLibFolder.getName()+File.separatorChar+newFO.getNameExt(); |
899 |
} |
900 |
volumeContent.add(LibrariesSupport.convertFileToURL(new File(name))); |
901 |
} |
902 |
content.put(volume, volumeContent); |
903 |
} |
904 |
final LibraryManager man = LibraryManager.forLocation(location); |
905 |
try { |
906 |
return ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Library>() { |
907 |
public Library run() throws IOException { |
908 |
String name = lib.getName(); |
909 |
if (generateLibraryUniqueName) { |
910 |
int index = 2; |
911 |
while (man.getLibrary(name) != null) { |
912 |
name = lib.getName() + "-" + index; |
913 |
index++; |
914 |
} |
915 |
} |
916 |
return man.createLibrary(lib.getType(), name, content); |
917 |
} |
918 |
}); |
919 |
} catch (MutexException ex) { |
920 |
throw (IOException)ex.getException(); |
921 |
} |
922 |
} |
923 |
|
924 |
/** |
925 |
* Generate unique file name for the given folder, base name and optionally extension. |
926 |
* @param baseFolder folder to generate new file name in |
927 |
* @param nameFileName file name without extension |
928 |
* @param extension can be null for folder |
929 |
* @return new file name without extension |
930 |
*/ |
931 |
private static String getUniqueName(FileObject baseFolder, String nameFileName, String extension) { |
932 |
int suffix = 2; |
933 |
String name = nameFileName; //NOI18N |
934 |
while (baseFolder.getFileObject(name + (extension != null ? "." + extension : "")) != null) { |
935 |
name = nameFileName + "-" + suffix; // NOI18N |
936 |
suffix++; |
937 |
} |
938 |
return name; |
939 |
} |
940 |
|
941 |
} |