0001: /*
0002: * The contents of this file are subject to the terms of the Common Development
0003: * and Distribution License (the License). You may not use this file except in
0004: * compliance with the License.
0005: *
0006: * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
0007: * or http://www.netbeans.org/cddl.txt.
0008: *
0009: * When distributing Covered Code, include this CDDL Header Notice in each file
0010: * and include the License file at http://www.netbeans.org/cddl.txt.
0011: * If applicable, add the following below the CDDL Header, with the fields
0012: * enclosed by brackets [] replaced by your own identifying information:
0013: * "Portions Copyrighted [year] [name of copyright owner]"
0014: *
0015: * The Original Software is NetBeans. The Initial Developer of the Original
0016: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0017: * Microsystems, Inc. All Rights Reserved.
0018: */
0019:
0020: package org.netbeans.modules.project.ant;
0021:
0022: import java.beans.PropertyChangeEvent;
0023: import java.beans.PropertyChangeListener;
0024: import java.beans.PropertyChangeSupport;
0025: import java.io.File;
0026: import java.io.FileInputStream;
0027: import java.io.IOException;
0028: import java.io.InputStream;
0029: import java.io.OutputStream;
0030: import java.lang.ref.Reference;
0031: import java.lang.ref.WeakReference;
0032: import java.net.MalformedURLException;
0033: import java.net.URISyntaxException;
0034: import java.net.URL;
0035: import java.util.ArrayList;
0036: import java.util.Arrays;
0037: import java.util.Collections;
0038: import java.util.HashMap;
0039: import java.util.HashSet;
0040: import java.util.List;
0041: import java.util.Map;
0042: import java.util.Properties;
0043: import java.util.Set;
0044: import java.util.logging.Logger;
0045: import java.util.regex.Matcher;
0046: import java.util.regex.Pattern;
0047: import javax.swing.JFileChooser;
0048: import javax.swing.event.ChangeListener;
0049: import javax.swing.filechooser.FileFilter;
0050: import org.netbeans.api.project.Project;
0051: import org.netbeans.api.project.ProjectManager;
0052: import org.netbeans.api.project.libraries.Library;
0053: import org.netbeans.api.project.libraries.LibraryManager;
0054: import org.netbeans.api.project.ui.OpenProjects;
0055: import org.netbeans.api.queries.CollocationQuery;
0056: import org.netbeans.api.queries.SharabilityQuery;
0057: import org.netbeans.spi.project.AuxiliaryConfiguration;
0058: import org.netbeans.spi.project.libraries.ArealLibraryProvider;
0059: import org.netbeans.spi.project.libraries.LibraryImplementation;
0060: import org.netbeans.spi.project.libraries.LibraryProvider;
0061: import org.netbeans.spi.project.libraries.LibraryStorageArea;
0062: import org.netbeans.spi.project.libraries.support.LibrariesSupport;
0063: import org.netbeans.spi.project.support.ant.AntProjectEvent;
0064: import org.netbeans.spi.project.support.ant.AntProjectHelper;
0065: import org.netbeans.spi.project.support.ant.AntProjectListener;
0066: import org.netbeans.spi.project.support.ant.EditableProperties;
0067: import org.netbeans.spi.project.support.ant.PropertyProvider;
0068: import org.netbeans.spi.project.support.ant.PropertyUtils;
0069: import org.netbeans.spi.queries.SharabilityQueryImplementation;
0070: import org.openide.filesystems.FileObject;
0071: import org.openide.filesystems.FileUtil;
0072: import org.openide.filesystems.URLMapper;
0073: import org.openide.util.ChangeSupport;
0074: import org.openide.util.Exceptions;
0075: import org.openide.util.Mutex;
0076: import org.openide.util.MutexException;
0077: import org.openide.util.NbBundle;
0078: import org.openide.util.NbCollections;
0079: import org.openide.util.RequestProcessor;
0080: import org.openide.util.Utilities;
0081: import org.openide.util.WeakListeners;
0082: import org.openide.xml.XMLUtil;
0083: import org.w3c.dom.Document;
0084: import org.w3c.dom.Element;
0085:
0086: /**
0087: * Supplier of libraries declared in open projects.
0088: * @see "issue #44035"
0089: */
0090: public class ProjectLibraryProvider
0091: implements
0092: ArealLibraryProvider<ProjectLibraryProvider.ProjectLibraryArea, ProjectLibraryProvider.ProjectLibraryImplementation>,
0093: PropertyChangeListener, AntProjectListener {
0094:
0095: private static final String NAMESPACE = "http://www.netbeans.org/ns/ant-project-libraries/1"; // NOI18N
0096: private static final String EL_LIBRARIES = "libraries"; // NOI18N
0097: private static final String EL_DEFINITIONS = "definitions"; // NOI18N
0098:
0099: private final PropertyChangeSupport pcs = new PropertyChangeSupport(
0100: this );
0101: private AntProjectListener apl;
0102:
0103: public static ProjectLibraryProvider INSTANCE;
0104:
0105: private volatile boolean listening = true;
0106: private final Map<ProjectLibraryArea, Reference<LP>> providers = new HashMap<ProjectLibraryArea, Reference<LP>>();
0107:
0108: /**
0109: * Default constructor for lookup.
0110: */
0111: public ProjectLibraryProvider() {
0112: INSTANCE = this ;
0113: }
0114:
0115: public Class<ProjectLibraryArea> areaType() {
0116: return ProjectLibraryArea.class;
0117: }
0118:
0119: public Class<ProjectLibraryImplementation> libraryType() {
0120: return ProjectLibraryImplementation.class;
0121: }
0122:
0123: @Override
0124: public String toString() {
0125: return "ProjectLibraryProvider"; // NOI18N
0126: }
0127:
0128: // ---- management of areas ----
0129:
0130: public void addPropertyChangeListener(
0131: PropertyChangeListener listener) {
0132: pcs.addPropertyChangeListener(listener);
0133: }
0134:
0135: public void removePropertyChangeListener(
0136: PropertyChangeListener listener) {
0137: pcs.removePropertyChangeListener(listener);
0138: }
0139:
0140: public Set<ProjectLibraryArea> getOpenAreas() {
0141: synchronized (this ) { // lazy init of OpenProjects-related stuff is better for unit testing
0142: if (apl == null) {
0143: apl = WeakListeners.create(AntProjectListener.class,
0144: this , null);
0145: OpenProjects.getDefault().addPropertyChangeListener(
0146: WeakListeners.propertyChange(this , OpenProjects
0147: .getDefault()));
0148: }
0149: }
0150: Set<ProjectLibraryArea> areas = new HashSet<ProjectLibraryArea>();
0151: for (Project p : OpenProjects.getDefault().getOpenProjects()) {
0152: AntProjectHelper helper = AntBasedProjectFactorySingleton
0153: .getHelperFor(p);
0154: if (helper == null) {
0155: // Not an Ant-based project; ignore.
0156: continue;
0157: }
0158: helper.removeAntProjectListener(apl);
0159: helper.addAntProjectListener(apl);
0160: Definitions def = findDefinitions(helper);
0161: if (def != null) {
0162: areas
0163: .add(new ProjectLibraryArea(
0164: def.mainPropertiesFile));
0165: }
0166: }
0167: return areas;
0168: }
0169:
0170: public ProjectLibraryArea createArea() {
0171: JFileChooser jfc = new JFileChooser();
0172: jfc.setApproveButtonText(NbBundle.getMessage(
0173: ProjectLibraryProvider.class,
0174: "ProjectLibraryProvider.open_or_create"));
0175: FileFilter filter = new FileFilter() {
0176: public boolean accept(File f) {
0177: return f.isDirectory()
0178: || (f.getName().endsWith(".properties") && !f
0179: .getName().endsWith(
0180: "-private.properties")); // NOI18N
0181: }
0182:
0183: public String getDescription() {
0184: return NbBundle.getMessage(
0185: ProjectLibraryProvider.class,
0186: "ProjectLibraryProvider.properties_files");
0187: }
0188: };
0189: jfc.setFileFilter(filter);
0190: FileUtil.preventFileChooserSymlinkTraversal(jfc, null); // XXX remember last-selected dir
0191: while (jfc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
0192: File f = jfc.getSelectedFile();
0193: if (filter.accept(f)) {
0194: return new ProjectLibraryArea(f);
0195: }
0196: // Else bad filename, reopen dialog. XXX would be better to just disable OK button, but not sure how...?
0197: }
0198: return null;
0199: }
0200:
0201: public ProjectLibraryArea loadArea(URL location) {
0202: if (location.getProtocol().equals("file")
0203: && location.getPath().endsWith(".properties")) { // NOI18N
0204: try {
0205: return new ProjectLibraryArea(
0206: new File(location.toURI()));
0207: } catch (URISyntaxException x) {
0208: Exceptions.printStackTrace(x);
0209: }
0210: }
0211: return null;
0212: }
0213:
0214: public void propertyChange(PropertyChangeEvent ev) {
0215: if (OpenProjects.PROPERTY_OPEN_PROJECTS.equals(ev
0216: .getPropertyName())) {
0217: pcs.firePropertyChange(
0218: ArealLibraryProvider.PROP_OPEN_AREAS, null, null);
0219: }
0220: }
0221:
0222: public void configurationXmlChanged(AntProjectEvent ev) {
0223: pcs.firePropertyChange(ArealLibraryProvider.PROP_OPEN_AREAS,
0224: null, null);
0225: }
0226:
0227: public void propertiesChanged(AntProjectEvent ev) {
0228: }
0229:
0230: // ---- management of libraries ----
0231:
0232: private final class LP implements
0233: LibraryProvider<ProjectLibraryImplementation>,
0234: FileChangeSupportListener {
0235:
0236: private final ProjectLibraryArea area;
0237: private final PropertyChangeSupport pcs = new PropertyChangeSupport(
0238: this );
0239: private final Map<String, ProjectLibraryImplementation> libraries;
0240:
0241: LP(ProjectLibraryArea area) {
0242: this .area = area;
0243: libraries = calculate(area);
0244: Definitions defs = new Definitions(area.mainPropertiesFile);
0245: FileChangeSupport.DEFAULT.addListener(this ,
0246: defs.mainPropertiesFile);
0247: FileChangeSupport.DEFAULT.addListener(this ,
0248: defs.privatePropertiesFile);
0249: }
0250:
0251: public synchronized ProjectLibraryImplementation[] getLibraries() {
0252: return libraries.values().toArray(
0253: new ProjectLibraryImplementation[libraries.size()]);
0254: }
0255:
0256: ProjectLibraryImplementation getLibrary(String name) {
0257: return libraries.get(name);
0258: }
0259:
0260: public void addPropertyChangeListener(
0261: PropertyChangeListener listener) {
0262: pcs.addPropertyChangeListener(listener);
0263: }
0264:
0265: public void removePropertyChangeListener(
0266: PropertyChangeListener listener) {
0267: pcs.removePropertyChangeListener(listener);
0268: }
0269:
0270: public void fileCreated(FileChangeSupportEvent event) {
0271: recalculate();
0272: }
0273:
0274: public void fileDeleted(FileChangeSupportEvent event) {
0275: recalculate();
0276: }
0277:
0278: public void fileModified(FileChangeSupportEvent event) {
0279: recalculate();
0280: }
0281:
0282: private void recalculate() {
0283: boolean fire;
0284: Map<ProjectLibraryImplementation, List<String>> toFire = new HashMap<ProjectLibraryImplementation, List<String>>();
0285: synchronized (this ) {
0286: fire = delta(libraries, calculate(area), toFire);
0287: }
0288: //#128784, don't fire in synchronized block..
0289: if (toFire.size() > 0) {
0290: for (ProjectLibraryImplementation impl : toFire
0291: .keySet()) {
0292: for (String prop : toFire.get(impl)) {
0293: impl.pcs.firePropertyChange(prop, null, null);
0294: }
0295: }
0296: }
0297: if (fire) {
0298: pcs.firePropertyChange(LibraryProvider.PROP_LIBRARIES,
0299: null, null);
0300: }
0301: }
0302:
0303: }
0304:
0305: public synchronized LP getLibraries(ProjectLibraryArea area) {
0306: Reference<LP> rlp = providers.get(area);
0307: LP lp = rlp != null ? rlp.get() : null;
0308: if (lp == null) {
0309: lp = new LP(area);
0310: providers.put(area, new WeakReference<LP>(lp));
0311: }
0312: return lp;
0313: }
0314:
0315: public ProjectLibraryImplementation createLibrary(String type,
0316: String name, ProjectLibraryArea area,
0317: Map<String, List<URL>> contents) throws IOException {
0318: File f = area.mainPropertiesFile;
0319: assert listening;
0320: listening = false;
0321: try {
0322: if (type.equals("j2se")) { // NOI18N
0323: replaceProperty(f, true, "libs." + name + ".classpath",
0324: ""); // NOI18N
0325: } else {
0326: replaceProperty(f, false, "libs." + name + ".type",
0327: type); // NOI18N
0328: }
0329: } finally {
0330: listening = true;
0331: }
0332: LP lp = getLibraries(area);
0333: boolean fire = delta(
0334: lp.libraries,
0335: calculate(area),
0336: new HashMap<ProjectLibraryImplementation, List<String>>());
0337: ProjectLibraryImplementation impl = lp.getLibrary(name);
0338: assert impl != null : name + " not found in " + f;
0339: for (Map.Entry<String, List<URL>> entry : contents.entrySet()) {
0340: impl.setContent(entry.getKey(), entry.getValue());
0341: }
0342: if (fire) {
0343: lp.pcs.firePropertyChange(LibraryProvider.PROP_LIBRARIES,
0344: null, null);
0345: }
0346: return impl;
0347: }
0348:
0349: public void remove(ProjectLibraryImplementation pli)
0350: throws IOException {
0351: String prefix = "libs." + pli.name + "."; // NOI18N
0352: // XXX run atomically to fire changes just once:
0353: for (File f : new File[] { pli.mainPropertiesFile,
0354: pli.privatePropertiesFile }) {
0355: for (String k : loadProperties(f).keySet()) {
0356: if (k.startsWith(prefix)) {
0357: replaceProperty(f, false, k);
0358: }
0359: }
0360: }
0361: }
0362:
0363: /** one definitions entry */
0364: private static final class Definitions {
0365: /** may or may not exist; in case you need to listen to it */
0366: final File mainPropertiesFile;
0367: /** similar to {@link #mainPropertiesFile} but for *-private.properties; null if main is not *.properties */
0368: final File privatePropertiesFile;
0369: private Map<String, String> properties;
0370:
0371: Definitions(File mainPropertiesFile) {
0372: this .mainPropertiesFile = mainPropertiesFile;
0373: String suffix = ".properties"; // NOI18N
0374: String name = mainPropertiesFile.getName();
0375: if (name.endsWith(suffix)) {
0376: privatePropertiesFile = new File(mainPropertiesFile
0377: .getParentFile(), name.substring(0, name
0378: .length()
0379: - suffix.length())
0380: + "-private" + suffix); // NOI18N
0381: } else {
0382: privatePropertiesFile = null;
0383: }
0384: }
0385:
0386: /** with ${base} resolved according to resolveBase; may be empty or have junk defs */
0387: synchronized Map<String, String> properties(boolean resolveBase) {
0388: if (properties == null) {
0389: properties = new HashMap<String, String>();
0390: String basedir = mainPropertiesFile.getParent();
0391: for (Map.Entry<String, String> entry : loadProperties(
0392: mainPropertiesFile).entrySet()) {
0393: String value = entry.getValue();
0394: if (resolveBase) {
0395: value = value.replace("${base}", basedir); // NOI18N
0396: }
0397: properties.put(entry.getKey(), value.replace('/',
0398: File.separatorChar));
0399: }
0400: if (privatePropertiesFile != null) {
0401: for (Map.Entry<String, String> entry : loadProperties(
0402: privatePropertiesFile).entrySet()) {
0403: String value = entry.getValue();
0404: if (resolveBase) {
0405: value = value.replace("${base}", basedir); // NOI18N
0406: }
0407: properties.put(entry.getKey(), value.replace(
0408: '/', File.separatorChar));
0409: }
0410: }
0411: }
0412: return properties;
0413: }
0414: }
0415:
0416: private static Definitions findDefinitions(AntProjectHelper helper) {
0417: String text = getLibrariesLocationText(helper
0418: .createAuxiliaryConfiguration());
0419: if (text != null) {
0420: File mainPropertiesFile = helper.resolveFile(text);
0421: if (mainPropertiesFile.getName().endsWith(".properties")) { // NOI18N
0422: return new Definitions(mainPropertiesFile);
0423: }
0424: }
0425: return null;
0426: }
0427:
0428: public static File getLibrariesLocation(AuxiliaryConfiguration aux,
0429: File projectFolder) {
0430: String text = getLibrariesLocationText(aux);
0431: if (text != null) {
0432: return PropertyUtils.resolveFile(projectFolder, text);
0433: }
0434: return null;
0435: }
0436:
0437: /**
0438: * Returns libraries location as text.
0439: */
0440: public static String getLibrariesLocationText(
0441: AuxiliaryConfiguration aux) {
0442: Element libraries = aux.getConfigurationFragment(EL_LIBRARIES,
0443: NAMESPACE, true);
0444: if (libraries != null) {
0445: for (Element definitions : Util.findSubElements(libraries)) {
0446: assert definitions.getLocalName()
0447: .equals(EL_DEFINITIONS) : definitions;
0448: String text = Util.findText(definitions);
0449: assert text != null : aux;
0450: return text;
0451: }
0452: }
0453: return null;
0454: }
0455:
0456: private static Map<String, String> loadProperties(File f) {
0457: if (!f.isFile()) {
0458: return Collections.emptyMap();
0459: }
0460: Properties p = new Properties();
0461: try {
0462: InputStream is = new FileInputStream(f);
0463: try {
0464: p.load(is);
0465: } finally {
0466: is.close();
0467: }
0468: return NbCollections.checkedMapByFilter(p, String.class,
0469: String.class, true);
0470: } catch (IOException x) {
0471: Exceptions.attachMessage(x, "Loading: " + f); // NOI18N
0472: Exceptions.printStackTrace(x);
0473: return Collections.emptyMap();
0474: }
0475: }
0476:
0477: //non private for test usage
0478: static final Pattern LIBS_LINE = Pattern
0479: .compile("libs\\.([^${}]+)\\.([^${}.]+)"); // NOI18N
0480:
0481: private static Map<String, ProjectLibraryImplementation> calculate(
0482: ProjectLibraryArea area) {
0483: Map<String, ProjectLibraryImplementation> libs = new HashMap<String, ProjectLibraryImplementation>();
0484: Definitions def = new Definitions(area.mainPropertiesFile);
0485: Map<String, Map<String, String>> data = new HashMap<String, Map<String, String>>();
0486: for (Map.Entry<String, String> entry : def.properties(false)
0487: .entrySet()) {
0488: Matcher match = LIBS_LINE.matcher(entry.getKey());
0489: if (!match.matches()) {
0490: continue;
0491: }
0492: String name = match.group(1);
0493: Map<String, String> subdata = data.get(name);
0494: if (subdata == null) {
0495: subdata = new HashMap<String, String>();
0496: data.put(name, subdata);
0497: }
0498: subdata.put(match.group(2), entry.getValue());
0499: }
0500: for (Map.Entry<String, Map<String, String>> entry : data
0501: .entrySet()) {
0502: String name = entry.getKey();
0503: String type = "j2se"; // NOI18N
0504: String description = null;
0505: Map<String, List<URL>> contents = new HashMap<String, List<URL>>();
0506: for (Map.Entry<String, String> subentry : entry.getValue()
0507: .entrySet()) {
0508: String k = subentry.getKey();
0509: if (k.equals("type")) { // NOI18N
0510: type = subentry.getValue();
0511: } else if (k.equals("name")) { // NOI18N
0512: // XXX currently overriding display name is not supported
0513: } else if (k.equals("description")) { // NOI18N
0514: description = subentry.getValue();
0515: } else {
0516: String[] path = PropertyUtils.tokenizePath(subentry
0517: .getValue());
0518: List<URL> volume = new ArrayList<URL>(path.length);
0519: for (String component : path) {
0520: String jarFolder = null;
0521: // "!/" was replaced in def.properties() with "!"+File.separatorChar
0522: int index = component.indexOf("!"
0523: + File.separatorChar); //NOI18N
0524: if (index != -1) {
0525: jarFolder = component.substring(index + 2);
0526: component = component.substring(0, index);
0527: }
0528: String f = component.replace('/',
0529: File.separatorChar).replace('\\',
0530: File.separatorChar).replace(
0531: "${base}" + File.separatorChar, "");
0532: File normalizedFile = FileUtil
0533: .normalizeFile(new File(component
0534: .replace('/',
0535: File.separatorChar)
0536: .replace('\\',
0537: File.separatorChar)
0538: .replace(
0539: "${base}",
0540: area.mainPropertiesFile
0541: .getParent())));
0542: try {
0543: URL u = LibrariesSupport
0544: .convertFilePathToURL(f);
0545: if (FileUtil.isArchiveFile(normalizedFile
0546: .toURI().toURL())) {
0547: u = FileUtil.getArchiveRoot(u);
0548: if (jarFolder != null) {
0549: u = appendJarFolder(u, jarFolder);
0550: }
0551: } else if (!u.toExternalForm()
0552: .endsWith("/")) {
0553: u = new URL(u.toExternalForm() + "/");
0554: }
0555: volume.add(u);
0556: } catch (MalformedURLException x) {
0557: Exceptions.printStackTrace(x);
0558: }
0559: }
0560: contents.put(k, volume);
0561: }
0562: }
0563: libs.put(name, new ProjectLibraryImplementation(
0564: def.mainPropertiesFile, def.privatePropertiesFile,
0565: type, name, description, contents));
0566: }
0567: return libs;
0568: }
0569:
0570: private boolean delta(
0571: Map<String, ProjectLibraryImplementation> libraries,
0572: Map<String, ProjectLibraryImplementation> newLibraries,
0573: Map<ProjectLibraryImplementation, List<String>> toFire) {
0574: if (!listening) {
0575: return false;
0576: }
0577: assert toFire != null;
0578: Set<String> added = new HashSet<String>(newLibraries.keySet());
0579: added.removeAll(libraries.keySet());
0580: Set<String> removed = new HashSet<String>();
0581: for (Map.Entry<String, ProjectLibraryImplementation> entry : libraries
0582: .entrySet()) {
0583: String name = entry.getKey();
0584: ProjectLibraryImplementation old = entry.getValue();
0585: ProjectLibraryImplementation nue = newLibraries.get(name);
0586: if (nue == null) {
0587: removed.add(name);
0588: continue;
0589: }
0590: if (!old.type.equals(nue.type)) {
0591: // Cannot fire this.
0592: added.add(name);
0593: removed.add(name);
0594: libraries.put(name, nue);
0595: continue;
0596: }
0597: assert old.name.equals(nue.name);
0598: if (!Utilities.compareObjects(old.description,
0599: nue.description)) {
0600: old.description = nue.description;
0601: List<String> props = toFire.get(old);
0602: if (props == null) {
0603: props = new ArrayList<String>();
0604: toFire.put(old, props);
0605: }
0606: props.add(LibraryImplementation.PROP_DESCRIPTION);
0607: }
0608: if (!old.contents.equals(nue.contents)) {
0609: old.contents = nue.contents;
0610: List<String> props = toFire.get(old);
0611: if (props == null) {
0612: props = new ArrayList<String>();
0613: toFire.put(old, props);
0614: }
0615: props.add(LibraryImplementation.PROP_CONTENT);
0616: }
0617: }
0618: for (String name : added) {
0619: libraries.put(name, newLibraries.get(name));
0620: }
0621: for (String name : removed) {
0622: libraries.remove(name);
0623: }
0624: return !added.isEmpty() || !removed.isEmpty();
0625: }
0626:
0627: /** for jar url this method returns path wihtin jar or null*/
0628: private static String getJarFolder(URL url) {
0629: assert "jar".equals(url.getProtocol()) : url;
0630: String u = url.toExternalForm();
0631: int index = u.indexOf("!/"); //NOI18N
0632: if (index != -1 && index + 2 < u.length()) {
0633: return u.substring(index + 2);
0634: }
0635: return null;
0636: }
0637:
0638: /** append path to given jar root url */
0639: private static URL appendJarFolder(URL u, String jarFolder) {
0640: assert "jar".equals(u.getProtocol())
0641: && u.toExternalForm().endsWith("!/") : u;
0642: try {
0643: return new URL(u + jarFolder.replace('\\', '/')); //NOI18N
0644: } catch (MalformedURLException e) {
0645: throw new AssertionError(e);
0646: }
0647: }
0648:
0649: static final class ProjectLibraryImplementation implements
0650: LibraryImplementation {
0651:
0652: final File mainPropertiesFile, privatePropertiesFile;
0653: final String type;
0654: String name;
0655: String description;
0656: Map<String, List<URL>> contents;
0657: final PropertyChangeSupport pcs = new PropertyChangeSupport(
0658: this );
0659:
0660: ProjectLibraryImplementation(File mainPropertiesFile,
0661: File privatePropertiesFile, String type, String name,
0662: String description, Map<String, List<URL>> contents) {
0663: this .mainPropertiesFile = mainPropertiesFile;
0664: this .privatePropertiesFile = privatePropertiesFile;
0665: this .type = type;
0666: this .name = name;
0667: this .description = description;
0668: this .contents = contents;
0669: }
0670:
0671: public String getType() {
0672: return type;
0673: }
0674:
0675: public String getName() {
0676: return name;
0677: }
0678:
0679: public String getDescription() {
0680: return description;
0681: }
0682:
0683: public String getLocalizingBundle() {
0684: return null;
0685: }
0686:
0687: public List<URL> getContent(String volumeType)
0688: throws IllegalArgumentException {
0689: List<URL> content = contents.get(volumeType);
0690: if (content == null) {
0691: content = Collections.emptyList();
0692: }
0693: return content;
0694: }
0695:
0696: public void setName(String name) {
0697: this .name = name;
0698: pcs.firePropertyChange(LibraryImplementation.PROP_NAME,
0699: null, null);
0700: throw new UnsupportedOperationException(); // XXX will anyone call this?
0701: }
0702:
0703: public void setDescription(String text) {
0704: throw new UnsupportedOperationException(); // XXX will anyone call this?
0705: }
0706:
0707: public void setContent(String volumeType, List<URL> path)
0708: throws IllegalArgumentException {
0709: if (path.equals(getContent(volumeType))) {
0710: return;
0711: }
0712: contents.put(volumeType, new ArrayList<URL>(path));
0713: List<String> value = new ArrayList<String>();
0714: for (URL entry : path) {
0715: String jarFolder = null;
0716: if ("jar".equals(entry.getProtocol())) { // NOI18N
0717: jarFolder = getJarFolder(entry);
0718: entry = FileUtil.getArchiveFile(entry);
0719: } else if (!"file".equals(entry.getProtocol())) { // NOI18N
0720: value.add(entry.toString());
0721: Logger
0722: .getLogger(
0723: ProjectLibraryProvider.class
0724: .getName())
0725: .fine(
0726: "Setting url="
0727: + entry
0728: + " as content for library volume type: "
0729: + volumeType);
0730: continue;
0731: }
0732: String p = LibrariesSupport.convertURLToFilePath(entry);
0733: File f = new File(p);
0734: // store properties always separated by '/' for consistency
0735: StringBuilder s = new StringBuilder();
0736: if (f.isAbsolute()) {
0737: s.append(f.getAbsolutePath().replace('\\', '/')); //NOI18N
0738: } else {
0739: s.append("${base}/" + p.replace('\\', '/')); // NOI18N
0740: }
0741: if (jarFolder != null) {
0742: s.append("!/"); // NOI18N
0743: s.append(jarFolder);
0744: }
0745: if (value.size() + 1 != path.size()) {
0746: s.append(File.pathSeparatorChar);
0747: }
0748: value.add(s.toString());
0749: }
0750: String key = "libs." + name + "." + volumeType; // NOI18N
0751: try {
0752: replaceProperty(mainPropertiesFile, true, key, value
0753: .toArray(new String[value.size()]));
0754: } catch (IOException x) {
0755: throw new IllegalArgumentException(x);
0756: }
0757: pcs.firePropertyChange(LibraryImplementation.PROP_CONTENT,
0758: null, null);
0759: }
0760:
0761: public void setLocalizingBundle(String resourceName) {
0762: throw new UnsupportedOperationException();
0763: }
0764:
0765: public void addPropertyChangeListener(PropertyChangeListener l) {
0766: pcs.addPropertyChangeListener(l);
0767: }
0768:
0769: public void removePropertyChangeListener(
0770: PropertyChangeListener l) {
0771: pcs.removePropertyChangeListener(l);
0772: }
0773:
0774: @Override
0775: public String toString() {
0776: return "ProjectLibraryImplementation[name=" + name
0777: + ",file=" + mainPropertiesFile + ",contents="
0778: + contents + "]"; // NOI18N
0779: }
0780:
0781: }
0782:
0783: private static void replaceProperty(File propfile,
0784: boolean classPathLikeValue, String key, String... value)
0785: throws IOException {
0786: EditableProperties ep = new EditableProperties();
0787: if (propfile.isFile()) {
0788: InputStream is = new FileInputStream(propfile);
0789: try {
0790: ep.load(is);
0791: } finally {
0792: is.close();
0793: }
0794: }
0795: if (Utilities.compareObjects(value, ep.getProperty(key))) {
0796: return;
0797: }
0798: if (value.length > 0) {
0799: if (classPathLikeValue) {
0800: ep.setProperty(key, value);
0801: } else {
0802: assert value.length == 1 : Arrays.asList(value);
0803: ep.setProperty(key, value[0]);
0804: }
0805: } else {
0806: ep.remove(key);
0807: }
0808: FileObject fo = FileUtil.createData(propfile);
0809: OutputStream os = fo.getOutputStream();
0810: try {
0811: ep.store(os);
0812: } finally {
0813: os.close();
0814: }
0815: }
0816:
0817: static final class ProjectLibraryArea implements LibraryStorageArea {
0818:
0819: final File mainPropertiesFile;
0820:
0821: ProjectLibraryArea(File mainPropertiesFile) {
0822: assert mainPropertiesFile.getName().endsWith(".properties") : mainPropertiesFile;
0823: this .mainPropertiesFile = mainPropertiesFile;
0824: }
0825:
0826: public String getDisplayName() {
0827: return mainPropertiesFile.getAbsolutePath();
0828: }
0829:
0830: public URL getLocation() {
0831: try {
0832: return mainPropertiesFile.toURI().toURL();
0833: } catch (MalformedURLException x) {
0834: throw new AssertionError(x);
0835: }
0836: }
0837:
0838: public boolean equals(Object obj) {
0839: return obj instanceof ProjectLibraryArea
0840: && ((ProjectLibraryArea) obj).mainPropertiesFile
0841: .equals(mainPropertiesFile);
0842: }
0843:
0844: public int hashCode() {
0845: return mainPropertiesFile.hashCode();
0846: }
0847:
0848: @Override
0849: public String toString() {
0850: return "ProjectLibraryArea[" + mainPropertiesFile + "]"; // NOI18N
0851: }
0852:
0853: }
0854:
0855: /**
0856: * Used from {@link AntProjectHelper#getProjectLibrariesPropertyProvider}.
0857: * @param helper a project
0858: * @return a provider of project library definition properties
0859: */
0860: public static PropertyProvider createPropertyProvider(
0861: final AntProjectHelper helper) {
0862: class PP implements PropertyProvider,
0863: FileChangeSupportListener, AntProjectListener {
0864: final ChangeSupport cs = new ChangeSupport(this );
0865: final Set<File> listeningTo = new HashSet<File>();
0866: {
0867: helper.addAntProjectListener(WeakListeners.create(
0868: AntProjectListener.class, this , helper));
0869: }
0870:
0871: private void listenTo(File f, Set<File> noLongerListeningTo) {
0872: if (f != null) {
0873: noLongerListeningTo.remove(f);
0874: if (listeningTo.add(f)) {
0875: FileChangeSupport.DEFAULT.addListener(this , f);
0876: }
0877: }
0878: }
0879:
0880: public synchronized Map<String, String> getProperties() {
0881: Map<String, String> m = new HashMap<String, String>();
0882: // XXX add an AntProjectListener
0883: Set<File> noLongerListeningTo = new HashSet<File>(
0884: listeningTo);
0885: Definitions def = findDefinitions(helper);
0886: if (def != null) {
0887: m.putAll(def.properties(true));
0888: listenTo(def.mainPropertiesFile,
0889: noLongerListeningTo);
0890: listenTo(def.privatePropertiesFile,
0891: noLongerListeningTo);
0892: }
0893: for (File f : noLongerListeningTo) {
0894: listeningTo.remove(f);
0895: FileChangeSupport.DEFAULT.removeListener(this , f);
0896: }
0897: return m;
0898: }
0899:
0900: public void addChangeListener(ChangeListener l) {
0901: cs.addChangeListener(l);
0902: }
0903:
0904: public void removeChangeListener(ChangeListener l) {
0905: cs.removeChangeListener(l);
0906: }
0907:
0908: public void fileCreated(FileChangeSupportEvent event) {
0909: fireChangeNowOrLater();
0910: }
0911:
0912: public void fileDeleted(FileChangeSupportEvent event) {
0913: fireChangeNowOrLater();
0914: }
0915:
0916: public void fileModified(FileChangeSupportEvent event) {
0917: fireChangeNowOrLater();
0918: }
0919:
0920: void fireChangeNowOrLater() {
0921: // See PropertyUtils.FilePropertyProvider.
0922: if (!cs.hasListeners()) {
0923: return;
0924: }
0925: final Mutex.Action<Void> action = new Mutex.Action<Void>() {
0926: public Void run() {
0927: cs.fireChange();
0928: return null;
0929: }
0930: };
0931: if (ProjectManager.mutex().isWriteAccess()
0932: || FIRE_CHANGES_SYNCH) {
0933: ProjectManager.mutex().readAccess(action);
0934: } else if (ProjectManager.mutex().isReadAccess()) {
0935: action.run();
0936: } else {
0937: RP.post(new Runnable() {
0938: public void run() {
0939: ProjectManager.mutex().readAccess(action);
0940: }
0941: });
0942: }
0943: }
0944:
0945: public void configurationXmlChanged(AntProjectEvent ev) {
0946: cs.fireChange();
0947: }
0948:
0949: public void propertiesChanged(AntProjectEvent ev) {
0950: }
0951: }
0952: return new PP();
0953: }
0954:
0955: private static final RequestProcessor RP = new RequestProcessor(
0956: "ProjectLibraryProvider.RP"); // NOI18N
0957: public static boolean FIRE_CHANGES_SYNCH = false; // used by tests
0958:
0959: /**
0960: * Is this library reachable from this project? Returns true if given library
0961: * is defined in libraries location associated with this project.
0962: */
0963: public static boolean isReachableLibrary(Library library,
0964: AntProjectHelper helper) {
0965: URL location = library.getManager().getLocation();
0966: if (location == null) {
0967: return false;
0968: }
0969: ProjectLibraryArea area = INSTANCE.loadArea(location);
0970: if (area == null) {
0971: return false;
0972: }
0973: ProjectLibraryImplementation pli = INSTANCE.getLibraries(area)
0974: .getLibrary(library.getName());
0975: if (pli == null) {
0976: return false;
0977: }
0978: Definitions def = findDefinitions(helper);
0979: if (def == null) {
0980: return false;
0981: }
0982: return def.mainPropertiesFile.equals(pli.mainPropertiesFile);
0983: }
0984:
0985: /**
0986: * Create element for shared libraries to store in project.xml.
0987: *
0988: * @param doc XML document
0989: * @param location project relative or absolute OS path; cannot be null
0990: * @return element
0991: */
0992: public static Element createLibrariesElement(Document doc,
0993: String location) {
0994: Element libraries = doc
0995: .createElementNS(NAMESPACE, EL_LIBRARIES);
0996: libraries.appendChild(
0997: libraries.getOwnerDocument().createElementNS(NAMESPACE,
0998: EL_DEFINITIONS)).appendChild(
0999: libraries.getOwnerDocument().createTextNode(location));
1000: return libraries;
1001: }
1002:
1003: /**
1004: * Used from {@link ReferenceHelper#getProjectLibraryManager}.
1005: */
1006: public static LibraryManager getProjectLibraryManager(
1007: AntProjectHelper helper) {
1008: Definitions defs = findDefinitions(helper);
1009: if (defs != null) {
1010: try {
1011: return LibraryManager
1012: .forLocation(defs.mainPropertiesFile.toURI()
1013: .toURL());
1014: } catch (MalformedURLException x) {
1015: Exceptions.printStackTrace(x);
1016: }
1017: }
1018: return null;
1019: }
1020:
1021: /**
1022: * Stores given libraries location in given project.
1023: */
1024: public static void setLibrariesLocation(AntProjectHelper helper,
1025: String librariesDefinition) {
1026: //TODO do we need to create new auxiliary configuration instance? feels like a hack, we should be
1027: // using the one from the project's lookup.
1028: if (librariesDefinition == null) {
1029: helper.createAuxiliaryConfiguration()
1030: .removeConfigurationFragment(EL_LIBRARIES,
1031: NAMESPACE, true);
1032: return;
1033: }
1034: Element libraries = helper
1035: .createAuxiliaryConfiguration()
1036: .getConfigurationFragment(EL_LIBRARIES, NAMESPACE, true);
1037: if (libraries == null) {
1038: libraries = XMLUtil.createDocument("dummy", null, null,
1039: null).createElementNS(NAMESPACE, EL_LIBRARIES); // NOI18N
1040: } else {
1041: List<Element> elements = Util.findSubElements(libraries);
1042: if (elements.size() == 1) {
1043: libraries.removeChild(elements.get(0));
1044: }
1045: }
1046: libraries.appendChild(
1047: libraries.getOwnerDocument().createElementNS(NAMESPACE,
1048: EL_DEFINITIONS)).appendChild(
1049: libraries.getOwnerDocument().createTextNode(
1050: librariesDefinition));
1051: helper.createAuxiliaryConfiguration().putConfigurationFragment(
1052: libraries, true);
1053: }
1054:
1055: /**
1056: * Used from {@link org.netbeans.spi.project.support.ant.SharabilityQueryImpl}.
1057: */
1058: public static List<String> getUnsharablePathsWithinProject(
1059: AntProjectHelper helper) {
1060: List<String> paths = new ArrayList<String>();
1061: Definitions defs = findDefinitions(helper);
1062: if (defs != null) {
1063: if (defs.privatePropertiesFile != null) {
1064: paths.add(defs.privatePropertiesFile.getAbsolutePath());
1065: }
1066: }
1067: return paths;
1068: }
1069:
1070: public static final class SharabilityQueryImpl implements
1071: SharabilityQueryImplementation {
1072:
1073: /** Default constructor for lookup. */
1074: public SharabilityQueryImpl() {
1075: }
1076:
1077: public int getSharability(File file) {
1078: if (file.getName().endsWith("-private.properties")) { // NOI18N
1079: return SharabilityQuery.NOT_SHARABLE;
1080: } else {
1081: return SharabilityQuery.UNKNOWN;
1082: }
1083: }
1084:
1085: }
1086:
1087: /**
1088: * Used from {@link org.netbeans.spi.project.support.ant.ReferenceHelper}.
1089: */
1090: public static Library copyLibrary(final Library lib,
1091: final URL location, final boolean generateLibraryUniqueName)
1092: throws IOException {
1093: assert LibrariesSupport.isAbsoluteURL(location);
1094: final File libBaseFolder = new File(LibrariesSupport
1095: .convertURLToFilePath(location)).getParentFile();
1096: FileObject sharedLibFolder;
1097: try {
1098: sharedLibFolder = ProjectManager.mutex().writeAccess(
1099: new Mutex.ExceptionAction<FileObject>() {
1100: public FileObject run() throws IOException {
1101: FileObject lf = FileUtil
1102: .toFileObject(libBaseFolder);
1103: if (lf == null) {
1104: lf = FileUtil
1105: .createFolder(libBaseFolder);
1106: }
1107: return lf.createFolder(getUniqueName(lf,
1108: lib.getName(), null));
1109: }
1110: });
1111: } catch (MutexException ex) {
1112: throw (IOException) ex.getException();
1113: }
1114: final Map<String, List<URL>> content = new HashMap<String, List<URL>>();
1115: String[] volumes = LibrariesSupport.getLibraryTypeProvider(
1116: lib.getType()).getSupportedVolumeTypes();
1117: for (String volume : volumes) {
1118: List<URL> volumeContent = new ArrayList<URL>();
1119: for (URL origlibEntry : lib.getContent(volume)) {
1120: URL libEntry = origlibEntry;
1121: String jarFolder = null;
1122: if ("jar".equals(libEntry.getProtocol())) { // NOI18N
1123: jarFolder = getJarFolder(libEntry);
1124: libEntry = FileUtil.getArchiveFile(libEntry);
1125: }
1126: FileObject libEntryFO = URLMapper
1127: .findFileObject(libEntry);
1128: if (libEntryFO == null) {
1129: if (!"file".equals(libEntry.getProtocol()) && // NOI18N
1130: !"nbinst".equals(libEntry.getProtocol())) { // NOI18N
1131: Logger.getLogger(
1132: ProjectLibraryProvider.class.getName())
1133: .info(
1134: "copyLibrary is ignoring entry "
1135: + libEntry);
1136: //this is probably exclusively urls to maven poms.
1137: continue;
1138: } else {
1139: Logger
1140: .getLogger(
1141: ProjectLibraryProvider.class
1142: .getName())
1143: .warning(
1144: "Library '"
1145: + lib.getDisplayName()
1146: + // NOI18N
1147: "' contains entry ("
1148: + libEntry
1149: + ") which does not exist. This entry is ignored and will not be copied to sharable libraries location."); // NOI18N
1150: continue;
1151: }
1152: }
1153: URL u;
1154: FileObject newFO;
1155: String name;
1156: if (CollocationQuery.areCollocated(libBaseFolder,
1157: FileUtil.toFile(libEntryFO))) {
1158: // if the jar/folder is in relation to the library folder (parent+child/same vcs)
1159: // don't replicate it but reference the original file.
1160: newFO = libEntryFO;
1161: name = PropertyUtils.relativizeFile(libBaseFolder,
1162: FileUtil.toFile(newFO));
1163: } else {
1164: if (libEntryFO.isFolder()) {
1165: newFO = FileChooserAccessory
1166: .copyFolderRecursively(libEntryFO,
1167: sharedLibFolder);
1168: name = sharedLibFolder.getNameExt()
1169: + File.separatorChar + newFO.getName()
1170: + File.separatorChar;
1171: } else {
1172: String libEntryName = getUniqueName(
1173: sharedLibFolder, libEntryFO.getName(),
1174: libEntryFO.getExt());
1175: newFO = FileUtil.copyFile(libEntryFO,
1176: sharedLibFolder, libEntryName);
1177: name = sharedLibFolder.getNameExt()
1178: + File.separatorChar
1179: + newFO.getNameExt();
1180: }
1181: }
1182: u = LibrariesSupport.convertFilePathToURL(name);
1183: if (FileUtil.isArchiveFile(newFO)) {
1184: u = FileUtil.getArchiveRoot(u);
1185: }
1186: if (jarFolder != null) {
1187: u = appendJarFolder(u, jarFolder);
1188: }
1189: volumeContent.add(u);
1190: }
1191: content.put(volume, volumeContent);
1192: }
1193: final LibraryManager man = LibraryManager.forLocation(location);
1194: try {
1195: return ProjectManager.mutex().writeAccess(
1196: new Mutex.ExceptionAction<Library>() {
1197: public Library run() throws IOException {
1198: String name = lib.getName();
1199: if (generateLibraryUniqueName) {
1200: int index = 2;
1201: while (man.getLibrary(name) != null) {
1202: name = lib.getName() + "-" + index;
1203: index++;
1204: }
1205: }
1206: return man.createLibrary(lib.getType(),
1207: name, content);
1208: }
1209: });
1210: } catch (MutexException ex) {
1211: throw (IOException) ex.getException();
1212: }
1213: }
1214:
1215: /**
1216: * Generate unique file name for the given folder, base name and optionally extension.
1217: * @param baseFolder folder to generate new file name in
1218: * @param nameFileName file name without extension
1219: * @param extension can be null for folder
1220: * @return new file name without extension
1221: */
1222: private static String getUniqueName(FileObject baseFolder,
1223: String nameFileName, String extension) {
1224: assert baseFolder != null;
1225: int suffix = 2;
1226: String name = nameFileName; //NOI18N
1227: while (baseFolder.getFileObject(name
1228: + (extension != null ? "." + extension : "")) != null) {
1229: name = nameFileName + "-" + suffix; // NOI18N
1230: suffix++;
1231: }
1232: return name;
1233: }
1234:
1235: }
|