blob: 2f471d9e39b20da7874e7816a372c1347a913115 [file] [log] [blame]
/*
* 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.maven.classpath;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.maven.artifact.Artifact;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.spi.java.classpath.ClassPathImplementation;
import org.netbeans.spi.java.classpath.FilteringPathResourceImplementation;
import org.netbeans.spi.java.classpath.PathResourceImplementation;
import org.openide.filesystems.FileUtil;
import org.openide.util.Utilities;
/**
*
* @author mkleint
*/
public abstract class AbstractProjectClassPathImpl implements ClassPathImplementation {
private static final Logger LOGGER = Logger.getLogger(AbstractProjectClassPathImpl.class.getName());
private final PropertyChangeSupport support = new PropertyChangeSupport(this);
private List<PathResourceImplementation> resources;
private NbMavenProjectImpl project;
protected AbstractProjectClassPathImpl(NbMavenProjectImpl proj) {
project = proj;
//TODO make weak or remove the listeners as well??
NbMavenProject.addPropertyChangeListener(proj, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
//explicitly listing both RESOURCE and PROJECT properties, it's unclear if both are required but since some other places call addWatchedPath but don't listen it's likely required
if (NbMavenProject.PROP_RESOURCE.equals(evt.getPropertyName()) || NbMavenProject.PROP_PROJECT.equals(evt.getPropertyName())) {
if (project.getProjectWatcher().isUnloadable()) {
return; //let's just continue with the old value, stripping classpath for broken project and re-creating it later serves no greater good.
}
List<PathResourceImplementation> newValues = getPath();
List<PathResourceImplementation> oldvalue;
boolean hasChanged;
synchronized (AbstractProjectClassPathImpl.this) {
oldvalue = resources;
hasChanged = hasChanged(oldvalue, newValues);
// System.out.println("checking=" + AbstractProjectClassPathImpl.this.getClass());
if (hasChanged) {
resources = newValues;
// System.out.println("old=" + oldvalue);
// System.out.println("new=" + newValues);
// System.out.println("firing change=" + AbstractProjectClassPathImpl.this.getClass());
}
}
if (hasChanged) {
support.firePropertyChange(ClassPathImplementation.PROP_RESOURCES, oldvalue, newValues);
}
}
}
});
}
protected NbMavenProjectImpl getProject() {
return project;
}
private boolean hasChanged(List<PathResourceImplementation> oldValues,
List<PathResourceImplementation> newValues) {
if (oldValues == null) {
return (newValues != null);
}
Iterator<PathResourceImplementation> it = oldValues.iterator();
ArrayList<PathResourceImplementation> nl = new ArrayList<PathResourceImplementation>();
nl.addAll(newValues);
while (it.hasNext()) {
PathResourceImplementation res = it.next();
URL oldUrl = res.getRoots()[0];
boolean found = false;
if (nl.isEmpty()) {
return true;
}
Iterator<PathResourceImplementation> inner = nl.iterator();
while (inner.hasNext()) {
PathResourceImplementation res2 = inner.next();
URL newUrl = res2.getRoots()[0];
if (newUrl.equals(oldUrl)) {
inner.remove();
found = true;
break;
}
}
if (!found) {
return true;
}
}
if (!nl.isEmpty()) {
return true;
}
return false;
}
protected final NbMavenProjectImpl getMavenProject() {
return project;
}
protected final void firePropertyChange(String propName, Object oldValue, Object newValue) {
support.firePropertyChange(propName, oldValue, newValue);
}
@Override
public synchronized List<PathResourceImplementation> getResources() {
if (resources == null) {
resources = this.getPath();
}
return resources;
}
abstract URI[] createPath();
private List<PathResourceImplementation> getPath() {
List<PathResourceImplementation> base = getPath(createPath(), new Includer() {
@Override public boolean includes(URL root, String resource) {
return AbstractProjectClassPathImpl.this.includes(root, resource);
}
});
return Collections.<PathResourceImplementation>unmodifiableList(base);
}
protected boolean includes(URL root, String resource) {
return true;
}
public interface Includer {
boolean includes(URL root, String resource);
}
public static List<PathResourceImplementation> getPath(URI[] pieces, final Includer includer) {
List<PathResourceImplementation> result = new ArrayList<PathResourceImplementation>();
for (int i = 0; i < pieces.length; i++) {
try {
// XXX would be cleaner to take a File[] if that is what these all are anyway!
final URL entry = FileUtil.urlForArchiveOrDir(Utilities.toFile(pieces[i]));
if (entry != null) {
result.add(new FilteringPathResourceImplementation() {
@Override public boolean includes(URL root, String resource) {
return includer != null ? includer.includes(root, resource) : true;
}
@Override public URL[] getRoots() {
return new URL[] {entry};
}
@Override public ClassPathImplementation getContent() {
return null;
}
@Override public void addPropertyChangeListener(PropertyChangeListener listener) {}
@Override public void removePropertyChangeListener(PropertyChangeListener listener) {}
});
}
} catch (IllegalArgumentException exc) {
Logger.getLogger(AbstractProjectClassPathImpl.class.getName()).log(Level.INFO, "Cannot use uri " + pieces[i] + " for classpath", exc);
}
}
return result;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener) {
synchronized (support) {
support.addPropertyChangeListener(propertyChangeListener);
}
}
@Override
public void removePropertyChangeListener(PropertyChangeListener propertyChangeListener) {
synchronized (support) {
support.removePropertyChangeListener(propertyChangeListener);
}
}
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
@Override public final boolean equals(Object obj) {
return getClass().isInstance(obj) && project.equals(((AbstractProjectClassPathImpl) obj).project);
}
@Override public final int hashCode() {
return project.hashCode() ^ getClass().hashCode();
}
/**
* Like {@link Artifact#getFile} but when a timestamped snapshot is locally downloaded, uses that instead.
*/
protected static File getFile(Artifact art) {
File f = art.getFile();
if (f != null) {
String baseVersion = art.getBaseVersion();
if (art.isSnapshot() && !art.getVersion().equals(baseVersion)) {
String name = f.getName();
int endOfVersion = name.lastIndexOf(/* DefaultRepositoryLayout.GROUP_SEPARATOR */'.');
String classifier = art.getClassifier();
if (classifier != null) {
endOfVersion -= classifier.length() + /* "-" */1;
}
if (endOfVersion > 0 && name.substring(0, endOfVersion).endsWith(baseVersion)) {
File f2 = new File(f.getParentFile(), name.substring(0, endOfVersion - baseVersion.length()) + art.getVersion() + name.substring(endOfVersion));
if (f2.isFile()) {
LOGGER.log(Level.FINE, "swapped {0} → {1}", new Object[] {f, f2});
return f2;
} else {
LOGGER.log(Level.FINE, "did not find predicted {0}", f2);
}
} else {
LOGGER.log(Level.FINE, "failed to match file pattern for {0} from {1}", new Object[] {f, art});
}
} else {
LOGGER.log(Level.FINEST, "not touching {0}", f);
}
} else {
LOGGER.log(Level.FINER, "no file for {0}", art);
}
return f;
}
}