org.netbeans.modules.project.ant.ProjectLibraryProvider Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.netbeans.modules.project.ant;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JFileChooser;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileFilter;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.libraries.Library;
import org.netbeans.api.project.libraries.LibraryManager;
import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.api.queries.CollocationQuery;
import org.netbeans.api.queries.SharabilityQuery;
import org.netbeans.modules.project.ant.ProjectLibraryProvider.ProjectLibraryArea;
import org.netbeans.modules.project.ant.ProjectLibraryProvider.ProjectLibraryImplementation;
import org.netbeans.modules.project.spi.intern.ProjectIDEServices;
import org.netbeans.spi.project.AuxiliaryConfiguration;
import org.netbeans.spi.project.libraries.ArealLibraryProvider;
import org.netbeans.spi.project.libraries.LibraryImplementation;
import org.netbeans.spi.project.libraries.LibraryImplementation2;
import org.netbeans.spi.project.libraries.LibraryImplementation3;
import org.netbeans.spi.project.libraries.LibraryProvider;
import org.netbeans.spi.project.libraries.LibraryStorageArea;
import org.netbeans.spi.project.libraries.support.LibrariesSupport;
import org.netbeans.spi.project.support.ant.AntProjectEvent;
import org.netbeans.spi.project.support.ant.AntProjectHelper;
import org.netbeans.spi.project.support.ant.AntProjectListener;
import org.netbeans.spi.project.support.ant.EditableProperties;
import org.netbeans.spi.project.support.ant.PropertyProvider;
import org.netbeans.spi.project.support.ant.PropertyUtils;
import org.netbeans.spi.project.support.ant.ReferenceHelper;
import org.netbeans.spi.queries.SharabilityQueryImplementation2;
import org.openide.filesystems.FileAttributeEvent;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
import org.openide.util.BaseUtilities;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;
import org.openide.util.Mutex;
import org.openide.util.MutexException;
import org.openide.util.NbBundle;
import org.openide.util.NbCollections;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.ServiceProvider;
import org.openide.xml.XMLUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Supplier of libraries declared in open projects.
* @see "issue #44035"
*/
@ServiceProvider(service=ArealLibraryProvider.class)
public class ProjectLibraryProvider implements ArealLibraryProvider, PropertyChangeListener, AntProjectListener {
private static final Logger LOG = Logger.getLogger(ProjectLibraryProvider.class.getName());
private static final String NAMESPACE = "http://www.netbeans.org/ns/ant-project-libraries/1"; // NOI18N
private static final String EL_LIBRARIES = "libraries"; // NOI18N
private static final String EL_DEFINITIONS = "definitions"; // NOI18N
private static final String SFX_DISPLAY_NAME = "displayName"; //NOI18N
private static final String PROP_PREFIX = "prop-"; //NOI18N
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private AntProjectListener apl;
public static ProjectLibraryProvider INSTANCE;
private volatile boolean listening = true;
private final Map> providers = new HashMap>();
/**
* Default constructor for lookup.
*/
public ProjectLibraryProvider() {
INSTANCE = this;
}
public Class areaType() {
return ProjectLibraryArea.class;
}
public Class libraryType() {
return ProjectLibraryImplementation.class;
}
@Override
public String toString() {
return "ProjectLibraryProvider"; // NOI18N
}
// ---- management of areas ----
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
public Set getOpenAreas() {
synchronized (this) { // lazy init of OpenProjects-related stuff is better for unit testing
if (apl == null) {
apl = WeakListeners.create(AntProjectListener.class, this, null);
OpenProjects.getDefault().addPropertyChangeListener(WeakListeners.propertyChange(this, OpenProjects.getDefault()));
}
}
Set areas = new HashSet();
for (Project p : OpenProjects.getDefault().getOpenProjects()) {
AntProjectHelper helper = AntBasedProjectFactorySingleton.getHelperFor(p);
if (helper == null) {
// Not an Ant-based project; ignore.
continue;
}
helper.removeAntProjectListener(apl);
helper.addAntProjectListener(apl);
Definitions def = findDefinitions(helper);
if (def != null) {
areas.add(new ProjectLibraryArea(def.mainPropertiesFile));
}
}
return areas;
}
public ProjectLibraryArea createArea() {
JFileChooser jfc = new JFileChooser();
jfc.setApproveButtonText(NbBundle.getMessage(ProjectLibraryProvider.class, "ProjectLibraryProvider.open_or_create"));
FileFilter filter = new FileFilter() {
public boolean accept(File f) {
return f.isDirectory() || (f.getName().endsWith(".properties") && !f.getName().endsWith("-private.properties")); // NOI18N
}
public String getDescription() {
return NbBundle.getMessage(ProjectLibraryProvider.class, "ProjectLibraryProvider.properties_files");
}
};
jfc.setFileFilter(filter);
FileUtil.preventFileChooserSymlinkTraversal(jfc, null); // XXX remember last-selected dir
while (jfc.showOpenDialog(Utilities.findDialogParent()) == JFileChooser.APPROVE_OPTION) {
File f = jfc.getSelectedFile();
if (filter.accept(f)) {
return new ProjectLibraryArea(f);
}
// Else bad filename, reopen dialog. XXX would be better to just disable OK button, but not sure how...?
}
return null;
}
public ProjectLibraryArea loadArea(URL location) {
if (location.getProtocol().equals("file") && location.getPath().endsWith(".properties")) { // NOI18N
try {
return new ProjectLibraryArea(BaseUtilities.toFile(location.toURI()));
} catch (URISyntaxException x) {
Exceptions.printStackTrace(x);
}
}
return null;
}
public void propertyChange(PropertyChangeEvent ev) {
if (OpenProjects.PROPERTY_OPEN_PROJECTS.equals(ev.getPropertyName())) {
pcs.firePropertyChange(ArealLibraryProvider.PROP_OPEN_AREAS, null, null);
}
}
public void configurationXmlChanged(AntProjectEvent ev) {
if (AntProjectHelper.PROJECT_XML_PATH.equals(ev.getPath())) {
pcs.firePropertyChange(ArealLibraryProvider.PROP_OPEN_AREAS, null, null);
}
}
public void propertiesChanged(AntProjectEvent ev) {}
// ---- management of libraries ----
private final class LP implements LibraryProvider, FileChangeListener {
private final ProjectLibraryArea area;
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private final Map libraries;
LP(ProjectLibraryArea area) {
this.area = area;
libraries = calculate(area);
Definitions defs = new Definitions(area.mainPropertiesFile);
FileUtil.addFileChangeListener(this, defs.mainPropertiesFile);
FileUtil.addFileChangeListener(this, defs.privatePropertiesFile);
}
public synchronized ProjectLibraryImplementation[] getLibraries() {
return libraries.values().toArray(new ProjectLibraryImplementation[libraries.size()]);
}
ProjectLibraryImplementation getLibrary(String name) {
return libraries.get(name);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
public void fileFolderCreated(FileEvent fe) {
recalculate();
}
public void fileDataCreated(FileEvent fe) {
recalculate();
}
public void fileChanged(FileEvent fe) {
recalculate();
}
public void fileDeleted(FileEvent fe) {
recalculate();
}
public void fileRenamed(FileRenameEvent fe) {
recalculate();
}
public void fileAttributeChanged(FileAttributeEvent fe) {
recalculate();
}
private void recalculate() {
boolean fire;
Map> toFire = new HashMap>();
synchronized (this) {
fire = delta(libraries, calculate(area), toFire);
}
//#128784, don't fire in synchronized block..
if (toFire.size() > 0) {
for (ProjectLibraryImplementation impl : toFire.keySet()) {
for (String prop : toFire.get(impl)) {
impl.pcs.firePropertyChange(prop, null, null);
}
}
}
if (fire) {
pcs.firePropertyChange(LibraryProvider.PROP_LIBRARIES, null, null);
}
}
}
public synchronized LP getLibraries(ProjectLibraryArea area) {
Reference rlp = providers.get(area);
LP lp = rlp != null ? rlp.get() : null;
if (lp == null) {
lp = new LP(area);
providers.put(area, new WeakReference(lp));
}
return lp;
}
public ProjectLibraryImplementation createLibrary(String type, String name, ProjectLibraryArea area, Map> contents) throws IOException {
File f = area.mainPropertiesFile;
assert listening;
listening = false;
try {
if (type.equals("j2se")) { // NOI18N
replaceProperty(f, true, "libs." + name + ".classpath", ""); // NOI18N
} else {
replaceProperty(f, false, "libs." + name + ".type", type); // NOI18N
}
} finally {
listening = true;
}
LP lp = getLibraries(area);
boolean fire = delta(lp.libraries, calculate(area), new HashMap>());
ProjectLibraryImplementation impl = lp.getLibrary(name);
assert impl != null : name + " not found in " + f;
for (Map.Entry> entry : contents.entrySet()) {
impl.setURIContent(entry.getKey(), entry.getValue());
}
if (fire) {
lp.pcs.firePropertyChange(LibraryProvider.PROP_LIBRARIES, null, null);
}
return impl;
}
public void remove(ProjectLibraryImplementation pli) throws IOException {
String prefix = "libs." + pli.name + "."; // NOI18N
// XXX run atomically to fire changes just once:
for (File f : new File[] {pli.mainPropertiesFile, pli.privatePropertiesFile}) {
for (String k : loadProperties(f).keySet()) {
if (k.startsWith(prefix)) {
replaceProperty(f, false, k);
}
}
}
ProjectLibraryArea pla = loadArea(BaseUtilities.toURI(pli.mainPropertiesFile).toURL());
if (pla != null) {
LP lp = getLibraries(pla);
if (lp.libraries.remove(pli.name) != null) {
// if library removal was successful it means we are running under FS Atomic action
// and file events trigerring recalculate() were not fired yet. fire PROP_LIBRARIES
// here to refresh libraries list:
lp.pcs.firePropertyChange(LibraryProvider.PROP_LIBRARIES, null, null);
}
}
}
/** one definitions entry */
private static final class Definitions {
/** may or may not exist; in case you need to listen to it */
final File mainPropertiesFile;
/** similar to {@link #mainPropertiesFile} but for *-private.properties; null if main is not *.properties */
final File privatePropertiesFile;
private Map properties;
Definitions(File mainPropertiesFile) {
this.mainPropertiesFile = mainPropertiesFile;
String suffix = ".properties"; // NOI18N
String name = mainPropertiesFile.getName();
if (name.endsWith(suffix)) {
privatePropertiesFile = new File(mainPropertiesFile.getParentFile(), name.substring(0, name.length() - suffix.length()) + "-private" + suffix); // NOI18N
} else {
privatePropertiesFile = null;
}
}
/** with ${base} resolved according to resolveBase; may be empty or have junk defs */
synchronized Map properties(boolean resolveBase) {
if (properties == null) {
properties = new HashMap();
String basedir = mainPropertiesFile.getParent();
for (Map.Entry entry : loadProperties(mainPropertiesFile).entrySet()) {
String value = entry.getValue();
if (resolveBase) {
value = value.replace("${base}", basedir); // NOI18N
}
properties.put(entry.getKey(), value.replace('/', File.separatorChar));
}
if (privatePropertiesFile != null) {
for (Map.Entry entry : loadProperties(privatePropertiesFile).entrySet()) {
String value = entry.getValue();
if (resolveBase) {
value = value.replace("${base}", basedir); // NOI18N
}
properties.put(entry.getKey(), value.replace('/', File.separatorChar));
}
}
}
return properties;
}
}
private static Definitions findDefinitions(AntProjectHelper helper) {
String text = getLibrariesLocationText(helper.createAuxiliaryConfiguration());
if (text != null) {
File mainPropertiesFile = helper.resolveFile(text);
if (mainPropertiesFile.getName().endsWith(".properties")) { // NOI18N
return new Definitions(mainPropertiesFile);
}
}
return null;
}
public static File getLibrariesLocation(AuxiliaryConfiguration aux, File projectFolder) {
String text = getLibrariesLocationText(aux);
if (text != null) {
return PropertyUtils.resolveFile(projectFolder, text);
}
return null;
}
/**
* Returns libraries location as text.
*/
public static String getLibrariesLocationText(AuxiliaryConfiguration aux) {
Element libraries = aux.getConfigurationFragment(EL_LIBRARIES, NAMESPACE, true);
if (libraries != null) {
for (Element definitions : XMLUtil.findSubElements(libraries)) {
assert definitions.getLocalName().equals(EL_DEFINITIONS) : definitions;
String text = XMLUtil.findText(definitions);
assert text != null : aux;
return text;
}
}
return null;
}
private static Map loadProperties(File f) {
if (!f.isFile()) {
return Collections.emptyMap();
}
Properties p = new Properties();
try {
InputStream is = new FileInputStream(f);
try {
p.load(is);
} finally {
is.close();
}
return NbCollections.checkedMapByFilter(p, String.class, String.class, true);
} catch (IOException x) {
LOG.log(Level.INFO, "Loading: " + f, x);
return Collections.emptyMap();
}
}
//non private for test usage
static final Pattern LIBS_LINE = Pattern.compile("libs\\.([^${}]+)\\.([^${}.]+)"); // NOI18N
private static Map calculate(ProjectLibraryArea area) {
Map libs = new HashMap();
Definitions def = new Definitions(area.mainPropertiesFile);
Map> data = new HashMap>();
for (Map.Entry entry : def.properties(false).entrySet()) {
Matcher match = LIBS_LINE.matcher(entry.getKey());
if (!match.matches()) {
continue;
}
String name = match.group(1);
Map subdata = data.get(name);
if (subdata == null) {
subdata = new HashMap();
data.put(name, subdata);
}
subdata.put(match.group(2), entry.getValue());
}
for (Map.Entry> entry : data.entrySet()) {
String name = entry.getKey();
String type = "j2se"; // NOI18N
String description = null;
String displayName = null;
Map> contents = new HashMap>();
Map properties = new HashMap();
for (Map.Entry subentry : entry.getValue().entrySet()) {
String k = subentry.getKey();
if (k.equals("type")) { // NOI18N
type = sanitizeSpaces(subentry.getValue());
} else if (k.equals("name")) { // NOI18N
// XXX currently overriding display name is not supported
} else if (k.equals("description")) { // NOI18N
description = subentry.getValue();
} else if (k.equals(SFX_DISPLAY_NAME)) { //NOI18N
displayName = subentry.getValue();
} else if (k.startsWith(PROP_PREFIX)) {
properties.put(k.substring(PROP_PREFIX.length()), subentry.getValue()); //NOI18N
} else {
final String[] path = sanitizeHttp(subentry.getKey(), PropertyUtils.tokenizePath(subentry.getValue()));
List volume = new ArrayList(path.length);
for (String component : path) {
component = sanitizeSpaces(component);
String jarFolder = null;
// "!/" was replaced in def.properties() with "!"+File.separatorChar
int index = component.indexOf("!"+File.separatorChar); //NOI18N
if (index != -1) {
jarFolder = component.substring(index+2);
component = component.substring(0, index);
}
String f = component.replace('/', File.separatorChar).replace('\\', File.separatorChar).replace("${base}"+File.separatorChar, "");
File normalizedFile = FileUtil.normalizeFile(new File(component.replace('/', File.separatorChar).replace('\\', File.separatorChar).replace("${base}", area.mainPropertiesFile.getParent())));
try {
URI u = LibrariesSupport.convertFilePathToURI(f);
if (FileUtil.isArchiveFile(BaseUtilities.toURI(normalizedFile).toURL())) {
u = appendJarFolder(u, jarFolder);
} else {
if (normalizedFile.exists() && !normalizedFile.isDirectory()) {
LOG.log(
Level.INFO,
"Ignoring wrong reference: {0} from library: {1}", //NOI18N
new Object[]{
component,
name
});
continue;
}
if (!u.getPath().endsWith("/")) { // NOI18N
u = new URI(u.toString() + "/"); // NOI18N
}
}
volume.add(u);
} catch (URISyntaxException x) {
Exceptions.printStackTrace(x);
} catch (MalformedURLException x) {
Exceptions.printStackTrace(x);
}
}
contents.put(k, volume);
}
}
libs.put(
name,
new ProjectLibraryImplementation(
def.mainPropertiesFile,
def.privatePropertiesFile,
type,
name,
description,
displayName,
contents,
properties));
}
return libs;
}
private boolean delta(Map libraries, Map newLibraries,
Map> toFire) {
if (!listening) {
return false;
}
assert toFire != null;
Set added = new HashSet(newLibraries.keySet());
added.removeAll(libraries.keySet());
Set removed = new HashSet();
for (Map.Entry entry : libraries.entrySet()) {
String name = entry.getKey();
ProjectLibraryImplementation old = entry.getValue();
ProjectLibraryImplementation nue = newLibraries.get(name);
if (nue == null) {
removed.add(name);
continue;
}
if (!old.type.equals(nue.type)) {
// Cannot fire this.
added.add(name);
removed.add(name);
libraries.put(name, nue);
continue;
}
assert old.name.equals(nue.name);
if (!BaseUtilities.compareObjects(old.description, nue.description)) {
old.description = nue.description;
List props = toFire.get(old);
if (props == null) {
props = new ArrayList();
toFire.put(old, props);
}
props.add(LibraryImplementation.PROP_DESCRIPTION);
}
if (!old.contents.equals(nue.contents)) {
old.contents = nue.contents;
List props = toFire.get(old);
if (props == null) {
props = new ArrayList();
toFire.put(old, props);
}
props.add(LibraryImplementation.PROP_CONTENT);
}
}
for (String name : added) {
libraries.put(name, newLibraries.get(name));
}
libraries.keySet().removeAll(removed);
return !added.isEmpty() || !removed.isEmpty();
}
/** for jar uri this method returns path wihtin jar or null*/
private static String getJarFolder(URI uri) {
String u = uri.toString();
int index = u.indexOf("!/"); //NOI18N
if (index != -1 && index + 2 < u.length()) {
return u.substring(index+2);
}
return null;
}
/** append path to given jar root uri */
private static URI appendJarFolder(URI u, String jarFolder) {
try {
if (u.isAbsolute()) {
return new URI("jar:" + u.toString() + "!/" + (jarFolder == null ? "" : jarFolder.replace('\\', '/'))); // NOI18N
} else {
return new URI(u.toString() + "!/" + (jarFolder == null ? "" : jarFolder.replace('\\', '/'))); // NOI18N
}
} catch (URISyntaxException e) {
throw new AssertionError(e);
}
}
/**
* Fixes the http(s) javadoc URLs stored in the libraries property file.
* For non javadoc volume types it does nothing. For javadoc volume types
* it appends http(s) protocol and path if the path starts with //.
* @param type
* @param entries
* @return
*/
private static String[] sanitizeHttp(final String type, final String... entries) {
//Only javadoc may contain http(s)
if (!"javadoc".equals(type)) { //NOI18N
return entries;
}
final Collection result = new ArrayList();
for (int i=0; i< entries.length; i++) {
if (i < entries.length - 1 && entries[i].matches("https?")) {
// #212877: Definitions.getProperties already converted to \, so have entries=["http", "\\server\path\"]
String schemeSpecificPart = entries[i + 1].replace('\\', '/');
if (schemeSpecificPart.startsWith("//")) {
result.add(entries[i] + ':' + schemeSpecificPart);
i++;
continue;
}
}
result.add(entries[i]);
}
return result.toArray(new String[result.size()]);
}
/**
* Removes the leading & trailing spaces from property value.
* The user edited nblibraries.properties may be corrupted by ending spaces (tabs),
* this method removes them.
* @param str to remove leading & trailing spaces from.
* @return fixed string
*/
@NonNull
private static String sanitizeSpaces(@NonNull final String str) {
return str.trim();
}
static final class ProjectLibraryImplementation implements LibraryImplementation2,LibraryImplementation3 {
final File mainPropertiesFile, privatePropertiesFile;
final String type;
String name;
String description;
String displayName;
Map> contents;
private Map properties;
final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
static Field libraryImplField;
static {
try {
libraryImplField = Library.class.getDeclaredField("impl"); //NOI18N
libraryImplField.setAccessible(true);
} catch (Exception exc) {
LOG.log(
Level.FINE,
"Cannot find field by reflection", //NOI18N
exc);
}
}
private String getGlobalLibBundle(Library lib) {
if (libraryImplField != null) {
try {
LibraryImplementation impl = (LibraryImplementation)libraryImplField.get(lib);
String toRet = impl.getLocalizingBundle();
return toRet;
} catch (Exception exc) {
LOG.log(
Level.FINE,
"Cannot access field by reflection", //NOI18N
exc);
}
}
return null;
}
ProjectLibraryImplementation(
File mainPropertiesFile,
File privatePropertiesFile,
String type,
String name,
final @NullAllowed String description,
final @NullAllowed String displayName,
final @NonNull Map> contents,
final @NonNull Map properties) {
this.mainPropertiesFile = mainPropertiesFile;
this.privatePropertiesFile = privatePropertiesFile;
this.type = type;
this.name = name;
this.description = description;
this.displayName = displayName;
this.contents = contents;
this.properties = properties;
}
public String getType() {
return type;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public String getLocalizingBundle() {
Library lib = LibraryManager.getDefault().getLibrary(name);
if (lib != null) {
return getGlobalLibBundle(lib);
}
return null;
}
@Override
public String getDisplayName() {
return displayName;
}
public List getContent(String volumeType) throws IllegalArgumentException {
List uris = getURIContent(volumeType);
List resolvedUrls = new ArrayList(uris.size());
for (URI u : uris) {
try {
resolvedUrls.add(LibrariesSupport.resolveLibraryEntryURI(BaseUtilities.toURI(mainPropertiesFile).toURL(), u).toURL());
} catch (MalformedURLException ex) {
LOG.log(Level.INFO, "#184304: " + u, ex);
}
}
return resolvedUrls;
}
public List getURIContent(String volumeType) throws IllegalArgumentException {
List content = contents.get(volumeType);
if (content == null) {
content = Collections.emptyList();
}
return content;
}
public void setName(String name) {
this.name = name;
pcs.firePropertyChange(LibraryImplementation.PROP_NAME, null, null);
throw new UnsupportedOperationException(); // XXX will anyone call this?
}
public void setDescription(String text) {
//NOP - dsescriptions are not supported
}
public void setContent(String volumeType, List path) throws IllegalArgumentException {
List uris = new ArrayList(path.size());
for (URL u : path) {
uris.add(URI.create(u.toExternalForm()));
}
setURIContent(volumeType, uris);
}
public void setURIContent(String volumeType, List path) throws IllegalArgumentException {
if (path.equals(contents.get(volumeType))) {
return;
}
contents.put(volumeType, new ArrayList(path));
List value = new ArrayList();
for (URI entry : path) {
String jarFolder = null;
if (entry.toString().contains("!/")) { // NOI18N
jarFolder = getJarFolder(entry);
entry = LibrariesSupport.getArchiveFile(entry);
} else if (entry.isAbsolute() && !"file".equals(entry.getScheme())) { // NOI18N
verifyAbsoluteURI(entry);
final StringBuilder sb = new StringBuilder(entry.toString());
if (value.size()+1 != path.size()) {
sb.append(File.pathSeparatorChar);
}
value.add(sb.toString());
LOG.log(
Level.FINE,
"Setting uri={0} as content for library volume type: {1}", //NOI18N
new Object[]{
entry,
volumeType
});
continue;
}
// store properties always separated by '/' for consistency
String entryPath = LibrariesSupport.convertURIToFilePath(entry).replace('\\', '/');
StringBuilder s = new StringBuilder();
if (entryPath.startsWith("${")) { // NOI18N
// if path start with an Ant property do not prefix it with "${base}".
// supports hand written customizations of nblibrararies.properties.
// for example libs.struts.classpath=${MAVEN_REPO}/struts/struts.jar
s.append(entryPath.replace('\\', '/')); // NOI18N
} else if (entry.isAbsolute()) {
verifyAbsoluteURI(entry);
s.append(entryPath);
} else {
s.append("${base}/").append(entryPath); // NOI18N
}
if (jarFolder != null) {
s.append("!/"); // NOI18N
s.append(jarFolder);
}
if (value.size()+1 != path.size()) {
s.append(File.pathSeparatorChar);
}
value.add(s.toString());
}
String key = "libs." + name + "." + volumeType; // NOI18N
try {
replaceProperty(mainPropertiesFile, true, key, value.toArray(new String[value.size()]));
} catch (IOException x) {
throw new IllegalArgumentException(x);
}
pcs.firePropertyChange(LibraryImplementation.PROP_CONTENT, null, null);
}
private void verifyAbsoluteURI(URI entry) throws IllegalArgumentException {
try {
entry.toURL();
} catch (MalformedURLException x) {
throw new IllegalArgumentException("#184304: " + entry + ": " + x, x);
}
}
public void setLocalizingBundle(String resourceName) {
throw new UnsupportedOperationException();
}
@Override
public void setDisplayName(final @NullAllowed String displayName) {
if (BaseUtilities.compareObjects(this.displayName, displayName)) {
return;
}
final String oldDisplayName = this.displayName;
this.displayName = displayName;
try {
final String key = String.format("libs.%s.%s",name, SFX_DISPLAY_NAME); //NOI18N
replaceProperty(
mainPropertiesFile,
false,
key,
displayName == null ? new String[0] : new String[]{displayName});
} catch (IOException x) {
throw new IllegalArgumentException(x);
}
pcs.firePropertyChange(PROP_DISPLAY_NAME, oldDisplayName, displayName);
}
public void addPropertyChangeListener(PropertyChangeListener l) {
pcs.addPropertyChangeListener(l);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
pcs.removePropertyChangeListener(l);
}
@Override
public String toString() {
return "ProjectLibraryImplementation[name=" + name + ",file=" + mainPropertiesFile + ",contents=" + contents + "]"; // NOI18N
}
@Override
public Map getProperties() {
return Collections.unmodifiableMap(properties);
}
@Override
public void setProperties(Map properties) {
if (BaseUtilities.compareObjects(this.properties, properties)) {
return;
}
final Map oldProperties = this.properties;
this.properties = new HashMap(properties);
try {
for (Map.Entry e : this.properties.entrySet()) {
final String key = String.format(
"libs.%s.%s%s", //NOI18N
name,
PROP_PREFIX,
e.getKey());
replaceProperty(
mainPropertiesFile,
false,
key,
e.getValue());
}
} catch (IOException x) {
throw new IllegalArgumentException(x);
}
pcs.firePropertyChange(PROP_PROPERTIES, oldProperties, this.properties);
}
}
private static void replaceProperty(File propfile, boolean classPathLikeValue, String key, String... value) throws IOException {
EditableProperties ep = new EditableProperties(true);
if (propfile.isFile()) {
InputStream is = new FileInputStream(propfile);
try {
ep.load(is);
} finally {
is.close();
}
}
if (BaseUtilities.compareObjects(value, ep.getProperty(key))) {
return;
}
if (value.length > 0) {
if (classPathLikeValue) {
ep.setProperty(key, value);
} else {
assert value.length == 1 : Arrays.asList(value);
ep.setProperty(key, value[0]);
}
} else {
ep.remove(key);
}
FileObject fo = FileUtil.createData(propfile);
OutputStream os = fo.getOutputStream();
try {
ep.store(os);
} finally {
os.close();
}
}
static final class ProjectLibraryArea implements LibraryStorageArea {
final File mainPropertiesFile;
ProjectLibraryArea(File mainPropertiesFile) {
assert mainPropertiesFile.getName().endsWith(".properties") : mainPropertiesFile;
this.mainPropertiesFile = mainPropertiesFile;
}
public String getDisplayName() {
return mainPropertiesFile.getAbsolutePath();
}
public URL getLocation() {
try {
return BaseUtilities.toURI(mainPropertiesFile).toURL();
} catch (MalformedURLException x) {
throw new AssertionError(x);
}
}
public @Override boolean equals(Object obj) {
return obj instanceof ProjectLibraryArea && ((ProjectLibraryArea) obj).mainPropertiesFile.equals(mainPropertiesFile);
}
public @Override int hashCode() {
return mainPropertiesFile.hashCode();
}
@Override
public String toString() {
return "ProjectLibraryArea[" + mainPropertiesFile + "]"; // NOI18N
}
}
/**
* Used from {@link AntProjectHelper#getProjectLibrariesPropertyProvider}.
* @param helper a project
* @return a provider of project library definition properties
*/
public static PropertyProvider createPropertyProvider(final AntProjectHelper helper) {
class PP implements PropertyProvider, FileChangeListener, AntProjectListener {
final ChangeSupport cs = new ChangeSupport(this);
final Set listeningTo = new HashSet();
{
helper.addAntProjectListener(WeakListeners.create(AntProjectListener.class, this, helper));
}
private void listenTo(File f, Set noLongerListeningTo) {
if (f != null) {
noLongerListeningTo.remove(f);
if (listeningTo.add(f)) {
FileUtil.addFileChangeListener(this, f);
}
}
}
public synchronized Map getProperties() {
Map m = new HashMap();
// XXX add an AntProjectListener
Set noLongerListeningTo = new HashSet(listeningTo);
Definitions def = findDefinitions(helper);
if (def != null) {
m.putAll(def.properties(true));
listenTo(def.mainPropertiesFile, noLongerListeningTo);
listenTo(def.privatePropertiesFile, noLongerListeningTo);
}
for (File f : noLongerListeningTo) {
listeningTo.remove(f);
FileUtil.removeFileChangeListener(this, f);
}
return m;
}
public void addChangeListener(ChangeListener l) {
cs.addChangeListener(l);
}
public void removeChangeListener(ChangeListener l) {
cs.removeChangeListener(l);
}
public void fileFolderCreated(FileEvent fe) {
fireChangeNowOrLater();
}
public void fileDataCreated(FileEvent fe) {
fireChangeNowOrLater();
}
public void fileChanged(FileEvent fe) {
fireChangeNowOrLater();
}
public void fileDeleted(FileEvent fe) {
fireChangeNowOrLater();
}
public void fileRenamed(FileRenameEvent fe) {
fireChangeNowOrLater();
}
public void fileAttributeChanged(FileAttributeEvent fe) {
fireChangeNowOrLater();
}
void fireChangeNowOrLater() {
// See PropertyUtils.FilePropertyProvider.
if (!cs.hasListeners()) {
return;
}
final Mutex.Action action = new Mutex.Action() {
public Void run() {
cs.fireChange();
return null;
}
};
if (ProjectManager.mutex().isWriteAccess() || FIRE_CHANGES_SYNCH) {
ProjectManager.mutex().readAccess(action);
} else if (ProjectManager.mutex().isReadAccess()) {
action.run();
} else {
RP.post(new Runnable() {
public void run() {
ProjectManager.mutex().readAccess(action);
}
});
}
}
public void configurationXmlChanged(AntProjectEvent ev) {
cs.fireChange();
}
public void propertiesChanged(AntProjectEvent ev) {}
}
return new PP();
}
private static final RequestProcessor RP = new RequestProcessor("ProjectLibraryProvider.RP"); // NOI18N
public static boolean FIRE_CHANGES_SYNCH = false; // used by tests
/**
* Is this library reachable from this project? Returns true if given library
* is defined in libraries location associated with this project.
*/
public static boolean isReachableLibrary(Library library, AntProjectHelper helper) {
URL location = library.getManager().getLocation();
if (location == null) {
return false;
}
ProjectLibraryArea area = INSTANCE.loadArea(location);
if (area == null) {
return false;
}
ProjectLibraryImplementation pli = INSTANCE.getLibraries(area).getLibrary(library.getName());
if (pli == null) {
return false;
}
Definitions def = findDefinitions(helper);
if (def == null) {
return false;
}
return def.mainPropertiesFile.equals(pli.mainPropertiesFile);
}
/**
* Create element for shared libraries to store in project.xml.
*
* @param doc XML document
* @param location project relative or absolute OS path; cannot be null
* @return element
*/
public static Element createLibrariesElement(Document doc, String location) {
Element libraries = doc.createElementNS(NAMESPACE, EL_LIBRARIES);
libraries.appendChild(libraries.getOwnerDocument().createElementNS(NAMESPACE, EL_DEFINITIONS)).
appendChild(libraries.getOwnerDocument().createTextNode(location));
return libraries;
}
/**
* Used from {@link ReferenceHelper#getProjectLibraryManager}.
*/
public static LibraryManager getProjectLibraryManager(AntProjectHelper helper) {
Definitions defs = findDefinitions(helper);
if (defs != null) {
try {
return LibraryManager.forLocation(BaseUtilities.toURI(defs.mainPropertiesFile).toURL());
} catch (MalformedURLException x) {
Exceptions.printStackTrace(x);
}
}
return null;
}
/**
* Stores given libraries location in given project.
*/
public static void setLibrariesLocation(AntProjectHelper helper, String librariesDefinition) {
//TODO do we need to create new auxiliary configuration instance? feels like a hack, we should be
// using the one from the project's lookup.
if (librariesDefinition == null) {
helper.createAuxiliaryConfiguration().removeConfigurationFragment(EL_LIBRARIES, NAMESPACE, true);
return;
}
Element libraries = helper.createAuxiliaryConfiguration().getConfigurationFragment(EL_LIBRARIES, NAMESPACE, true);
if (libraries == null) {
libraries = XMLUtil.createDocument("dummy", null, null, null).createElementNS(NAMESPACE, EL_LIBRARIES); // NOI18N
} else {
List elements = XMLUtil.findSubElements(libraries);
if (elements.size() == 1) {
libraries.removeChild(elements.get(0));
}
}
libraries.appendChild(libraries.getOwnerDocument().createElementNS(NAMESPACE, EL_DEFINITIONS)).
appendChild(libraries.getOwnerDocument().createTextNode(librariesDefinition));
helper.createAuxiliaryConfiguration().putConfigurationFragment(libraries, true);
}
/**
* Used from {@link org.netbeans.spi.project.support.ant.SharabilityQueryImpl}.
*/
public static List getUnsharablePathsWithinProject(AntProjectHelper helper) {
List paths = new ArrayList();
Definitions defs = findDefinitions(helper);
if (defs != null) {
if (defs.privatePropertiesFile != null) {
paths.add(defs.privatePropertiesFile.getAbsolutePath());
}
}
return paths;
}
@ServiceProvider(service=SharabilityQueryImplementation2.class, position=50)
public static final class SharabilityQueryImpl implements SharabilityQueryImplementation2 {
@Override public SharabilityQuery.Sharability getSharability(URI uri) {
if (uri.toString().endsWith("-private.properties")) { // NOI18N
return SharabilityQuery.Sharability.NOT_SHARABLE;
} else {
return SharabilityQuery.Sharability.UNKNOWN;
}
}
}
/**
* Used from {@link org.netbeans.spi.project.support.ant.ReferenceHelper}.
*/
public static Library copyLibrary(final Library lib, final URL location,
final boolean generateLibraryUniqueName) throws IOException {
final File libBaseFolder = BaseUtilities.toFile(URI.create(location.toExternalForm())).getParentFile();
FileObject sharedLibFolder = null;
final Map> content = new HashMap>();
String[] volumes = LibrariesSupport.getLibraryTypeProvider(lib.getType()).getSupportedVolumeTypes();
for (String volume : volumes) {
List volumeContent = new ArrayList();
for (URL origlibEntry : lib.getContent(volume)) {
URL libEntry = origlibEntry;
String jarFolder = null;
if ("jar".equals(libEntry.getProtocol())) { // NOI18N
jarFolder = getJarFolder(URI.create(libEntry.toExternalForm()));
libEntry = FileUtil.getArchiveFile(libEntry);
}
FileObject libEntryFO = URLMapper.findFileObject(libEntry);
if (libEntryFO == null) {
if (!"file".equals(libEntry.getProtocol()) && // NOI18N
!"nbinst".equals(libEntry.getProtocol())) { // NOI18N
LOG.info("copyLibrary is ignoring entry "+libEntry);
//this is probably exclusively urls to maven poms.
continue;
} else {
LOG.log(
Level.WARNING,
"Library ''{0}'' contains entry ({1}) which does not exist. This entry is ignored and will not be copied to sharable libraries location.", // NOI18N
new Object[]{
lib.getDisplayName(),
libEntry
});
continue;
}
}
URI u;
FileObject newFO;
String name;
if (CollocationQuery.areCollocated(BaseUtilities.toURI(libBaseFolder), libEntryFO.toURI())) {
// if the jar/folder is in relation to the library folder (parent+child/same vcs)
// don't replicate it but reference the original file.
newFO = libEntryFO;
name = PropertyUtils.relativizeFile(libBaseFolder, FileUtil.toFile(newFO));
} else {
if (sharedLibFolder == null) {
sharedLibFolder = getSharedLibFolder(libBaseFolder, lib);
}
if (libEntryFO.isFolder()) {
newFO = copyFolderRecursively(libEntryFO, sharedLibFolder);
name = sharedLibFolder.getNameExt()+File.separatorChar+newFO.getName()+File.separatorChar;
} else {
String libEntryName = getUniqueName(sharedLibFolder, libEntryFO.getName(), libEntryFO.getExt());
newFO = FileUtil.copyFile(libEntryFO, sharedLibFolder, libEntryName);
name = sharedLibFolder.getNameExt()+File.separatorChar+newFO.getNameExt();
}
}
u = LibrariesSupport.convertFilePathToURI(name);
if (FileUtil.isArchiveFile(newFO)) {
u = appendJarFolder(u, jarFolder);
}
volumeContent.add(u);
}
content.put(volume, volumeContent);
}
final LibraryManager man = LibraryManager.forLocation(location);
try {
return ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction() {
public Library run() throws IOException {
String name = lib.getName();
if (generateLibraryUniqueName) {
int index = 2;
while (man.getLibrary(name) != null) {
name = lib.getName() + "-" + index;
index++;
}
}
String displayName = lib.getDisplayName();
if (name.equals(displayName)) {
//No need to set displayName when it's same as name
displayName = null;
}
return man.createURILibrary(lib.getType(), name, displayName, lib.getDescription(), content, lib.getProperties());
}
});
} catch (MutexException ex) {
throw (IOException)ex.getException();
}
}
private static FileObject getSharedLibFolder(final File libBaseFolder, final Library lib) throws IOException {
FileObject sharedLibFolder;
try {
sharedLibFolder = ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction() {
public FileObject run() throws IOException {
FileObject lf = FileUtil.toFileObject(libBaseFolder);
if (lf == null) {
lf = FileUtil.createFolder(libBaseFolder);
}
return lf.createFolder(getUniqueName(lf, lib.getName(), null));
}
});
} catch (MutexException ex) {
throw (IOException)ex.getException();
}
return sharedLibFolder;
}
/**
* Generate unique file name for the given folder, base name and optionally extension.
* @param baseFolder folder to generate new file name in
* @param nameFileName file name without extension
* @param extension can be null for folder
* @return new file name without extension
*/
private static String getUniqueName(FileObject baseFolder, String nameFileName, String extension) {
assert baseFolder != null;
int suffix = 2;
String name = nameFileName; //NOI18N
while (baseFolder.getFileObject(name + (extension != null ? "." + extension : "")) != null) {
name = nameFileName + "-" + suffix; // NOI18N
suffix++;
}
return name;
}
private static FileObject copyFolderRecursively(final FileObject sourceFolder, final FileObject destination) throws IOException {
FileUtil.runAtomicAction(new FileSystem.AtomicAction() {
@Override public void run() throws IOException {
assert sourceFolder.isFolder() : sourceFolder;
assert destination.isFolder() : destination;
FileObject destinationSubFolder = destination.getFileObject(sourceFolder.getName());
if (destinationSubFolder == null) {
destinationSubFolder = destination.createFolder(sourceFolder.getName());
}
for (FileObject fo : sourceFolder.getChildren()) {
if (fo.isFolder()) {
copyFolderRecursively(fo, destinationSubFolder);
} else {
FileObject foExists = destinationSubFolder.getFileObject(fo.getName(), fo.getExt());
if (foExists != null) {
foExists.delete();
}
FileUtil.copyFile(fo, destinationSubFolder, fo.getName(), fo.getExt());
}
}
}
});
return destination.getFileObject(sourceFolder.getName());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy