diff --git a/java.j2seproject/nbproject/project.xml b/java.j2seproject/nbproject/project.xml
--- a/java.j2seproject/nbproject/project.xml
+++ b/java.j2seproject/nbproject/project.xml
@@ -163,6 +163,14 @@
+ org.netbeans.modules.java.preprocessorbridge
+
+
+
+ 1.41
+
+
+
org.netbeans.modules.java.project
diff --git a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEActionProvider.java b/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEActionProvider.java
--- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEActionProvider.java
+++ b/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEActionProvider.java
@@ -48,7 +48,11 @@
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
+import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
@@ -57,14 +61,19 @@
import java.util.Map;
import java.util.Properties;
import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.tools.ant.module.api.support.ActionUtils;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.annotations.common.StaticResource;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.source.BuildArtifactMapper;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
import org.netbeans.modules.java.api.common.SourceRoots;
import org.netbeans.modules.java.api.common.ant.UpdateHelper;
import org.netbeans.modules.java.api.common.project.ProjectProperties;
@@ -72,16 +81,23 @@
import org.netbeans.modules.java.api.common.project.BaseActionProvider.Callback3;
import org.netbeans.modules.java.api.common.project.ProjectConfigurations;
import org.netbeans.modules.java.j2seproject.api.J2SEBuildPropertiesProvider;
+import org.netbeans.modules.java.preprocessorbridge.spi.CompileOnSaveAction;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.LookupProvider;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.netbeans.spi.project.SingleMethod;
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
+import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
+import org.openide.modules.Places;
+import org.openide.util.BaseUtilities;
+import org.openide.util.Exceptions;
import org.openide.util.Lookup;
+import org.openide.util.Pair;
import org.openide.util.Parameters;
import org.openide.util.WeakListeners;
+import org.openide.util.lookup.ServiceProvider;
/** Action provider of the J2SE project. This is the place where to do
* strange things to J2SE actions. E.g. compile-single.
@@ -341,8 +357,18 @@
}
private static final class CosAction implements BuildArtifactMapper.ArtifactsUpdated,
- PropertyChangeListener {
+ CompileOnSaveAction, PropertyChangeListener {
+ private static Map> instances = new WeakHashMap<>();
private static final String COS_UPDATED = "$cos.update"; //NOI18N
+ private static final String COS_CUSTOM = "$cos.update.custom"; //NOI18N
+ private static final String PROP_TARGET = "cos.update.target.internal"; //NOI18N
+ private static final String PROP_SCRIPT = "cos.update.script.internal"; //NOI18N
+ private static final String PROP_SRCDIR = "cos.src.dir.internal"; //NOI18N
+ private static final String PROP_INCLUDES ="cos.includes.internal"; //NOI18N
+ private static final String SNIPPETS = "executor-snippets"; //NOI18N
+ private static final String SCRIPT = "cos-update.xml"; //NOI18N
+ private static final String TARGET = "cos-update-internal"; //NOI18N
+ private static final String SCRIPT_TEMPLATE = "/org/netbeans/modules/java/j2seproject/resources/cos-update-snippet.xml"; //NOI18N
private static final Object NONE = new Object();
private final J2SEActionProvider owner;
private final PropertyEvaluator eval;
@@ -351,6 +377,7 @@
private final BuildArtifactMapper mapper;
private final Map*@GuardedBy("this")*/URL,BuildArtifactMapper.ArtifactsUpdated> currentListeners;
private volatile Object targetCache;
+ private volatile Boolean enabledCache;
private CosAction(
@NonNull final J2SEActionProvider owner,
@@ -367,28 +394,55 @@
this.src.addPropertyChangeListener(WeakListeners.propertyChange(this, this.src));
this.tests.addPropertyChangeListener(WeakListeners.propertyChange(this, this.tests));
updateRootsListeners();
+ instances.put(owner.getProject(), new WeakReference<>(this));
}
@Override
+ public boolean isUpdateClasses() {
+ return getTarget() != null && isCustomUpdate();
+ }
+
+ @Override
+ public boolean isUpdateResources() {
+ return getTarget() != null && isCustomUpdate();
+ }
+
+ @Override
+ public Boolean performAction(Context ctx) throws IOException {
+ switch (ctx.getOperation()) {
+ case UPDATE:
+ return performUpdate(ctx);
+ case CLEAN:
+ return performClean(ctx);
+ case SYNC:
+ return performSync(ctx);
+ default:
+ throw new IllegalArgumentException(String.valueOf(ctx.getOperation()));
+ }
+ }
+
+ @Override
public void artifactsUpdated(@NonNull final Iterable artifacts) {
- final String target = getTarget();
- if (target != null) {
- final FileObject buildXml = owner.findBuildXml();
- if (buildXml != null) {
- try {
- ActionUtils.runTarget(
- buildXml,
- new String[] {target},
- null,
- null);
- } catch (IOException ioe) {
- LOG.log(
- Level.WARNING,
- "Cannot execute pos compile on save target: {0} in: {1}", //NOI18N
- new Object[]{
- target,
- FileUtil.getFileDisplayName(buildXml)
- });
+ if (!isCustomUpdate()) {
+ final String target = getTarget();
+ if (target != null) {
+ final FileObject buildXml = owner.findBuildXml();
+ if (buildXml != null) {
+ try {
+ ActionUtils.runTarget(
+ buildXml,
+ new String[] {target},
+ null,
+ null);
+ } catch (IOException ioe) {
+ LOG.log(
+ Level.WARNING,
+ "Cannot execute pos compile on save target: {0} in: {1}", //NOI18N
+ new Object[]{
+ target,
+ FileUtil.getFileDisplayName(buildXml)
+ });
+ }
}
}
}
@@ -397,9 +451,14 @@
@Override
public void propertyChange(@NonNull final PropertyChangeEvent evt) {
final String name = evt.getPropertyName();
- if (name == null || COS_UPDATED.equals(name)) {
+ if (name == null) {
targetCache = null;
- } else if (SourceRoots.PROP_ROOTS.equals(name)) {
+ enabledCache = null;
+ } else if (COS_UPDATED.equals(name)) {
+ targetCache = null;
+ } else if (COS_CUSTOM.equals(name)) {
+ enabledCache = null;
+ }else if (SourceRoots.PROP_ROOTS.equals(name)) {
updateRootsListeners();
}
}
@@ -440,6 +499,123 @@
(String) target :
null;
}
+
+ private boolean isCustomUpdate() {
+ Boolean res = enabledCache;
+ if (res == null) {
+ final String val = eval.getProperty(COS_CUSTOM);
+ res = enabledCache = Boolean.valueOf(val);
+ }
+ return res;
+ }
+
+ @CheckForNull
+ private Boolean performUpdate(@NonNull final Context ctx) {
+ final String target = getTarget();
+ if (target != null) {
+ final FileObject buildXml = owner.findBuildXml();
+ if (buildXml != null) {
+ try {
+ final FileObject cosScript = getCosScript();
+ final Iterable extends File> updated = ctx.getUpdated();
+ final Iterable extends File> deleted = ctx.getDeleted();
+ final File root = ctx.isCopyResources() ?
+ BaseUtilities.toFile(ctx.getSourceRoot().toURI()) :
+ ctx.getCacheRoot();
+ final String includes = createIncludes(root, updated);
+ if (includes != null) {
+ final Properties props = new Properties();
+ props.setProperty(PROP_TARGET, target);
+ props.setProperty(PROP_SCRIPT, FileUtil.toFile(buildXml).getAbsolutePath());
+ props.setProperty(PROP_SRCDIR, root.getAbsolutePath());
+ props.setProperty(PROP_INCLUDES, includes);
+ ActionUtils.runTarget(
+ cosScript,
+ new String[] {TARGET},
+ props,
+ null);
+ } else {
+ LOG.warning("BuildArtifactMapper artifacts do not provide attributes."); //NOI18N
+ }
+ } catch (IOException | URISyntaxException e) {
+ LOG.log(
+ Level.WARNING,
+ "Cannot execute update targer on save target: {0} in: {1} due to: {2}", //NOI18N
+ new Object[]{
+ target,
+ FileUtil.getFileDisplayName(buildXml),
+ e.getMessage()
+ });
+ }
+ }
+ }
+ return true;
+ }
+
+ @CheckForNull
+ private Boolean performClean(@NonNull final Context ctx) {
+ //Not sure what to do
+ return null;
+ }
+
+ @CheckForNull
+ private Boolean performSync(@NonNull final Context ctx) {
+ //Not sure what to do
+ return null;
+ }
+
+ @NonNull
+ private FileObject getCosScript() throws IOException {
+ final FileObject snippets = FileUtil.createFolder(
+ Places.getCacheSubdirectory(SNIPPETS));
+ FileObject cosScript = snippets.getFileObject(SCRIPT);
+ if (cosScript == null) {
+ cosScript = FileUtil.createData(snippets, SCRIPT);
+ final FileLock lock = cosScript.lock();
+ try (InputStream in = getClass().getResourceAsStream(SCRIPT_TEMPLATE);
+ OutputStream out = cosScript.getOutputStream(lock)) {
+ FileUtil.copy(in, out);
+ } finally {
+ lock.releaseLock();
+ }
+ }
+ return cosScript;
+ }
+
+ @CheckForNull
+ private static String createIncludes(
+ @NonNull final File root,
+ @NonNull final Iterable extends File> artifacts) {
+ final StringBuilder include = new StringBuilder();
+ for (File f : artifacts) {
+ if (include.length() > 0) {
+ include.append(','); //NOI18N
+ }
+ include.append(relativize(f,root));
+ }
+ return include.length() == 0 ?
+ null :
+ include.toString();
+ }
+
+ private static String relativize(
+ @NonNull final File file,
+ @NonNull final File folder) {
+ final String folderPath = folder.getAbsolutePath();
+ int start = folderPath.length();
+ if (!folderPath.endsWith(File.separator)) {
+ start++;
+ }
+ return file.getAbsolutePath().substring(start);
+ }
+
+ @CheckForNull
+ static CosAction getInstance(@NonNull final Project p) {
+ final Reference r = instances.get(p);
+ return r != null ?
+ r.get() :
+ null;
+ }
private static final class WeakArtifactUpdated extends WeakReference
implements BuildArtifactMapper.ArtifactsUpdated, Runnable {
@@ -473,4 +649,26 @@
}
}
}
+
+ @ServiceProvider(service = CompileOnSaveAction.Provider.class, position = 10_000)
+ public static final class Provider implements CompileOnSaveAction.Provider {
+
+ @Override
+ public CompileOnSaveAction forRoot(URL root) {
+ try {
+ final Project p = FileOwnerQuery.getOwner(root.toURI());
+ if (p != null) {
+ p.getLookup().lookup(ActionProvider.class).getSupportedActions(); //Force initialization
+ final CosAction action = CosAction.getInstance(p);
+ if (action != null && action.isUpdateClasses()) {
+ return action;
+ }
+ }
+ } catch (URISyntaxException e) {
+ Exceptions.printStackTrace(e);
+ }
+ return null;
+ }
+
+ }
}
diff --git a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/resources/cos-update-snippet.xml b/java.j2seproject/src/org/netbeans/modules/java/j2seproject/resources/cos-update-snippet.xml
new file mode 100644
--- /dev/null
+++ b/java.j2seproject/src/org/netbeans/modules/java/j2seproject/resources/cos-update-snippet.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
diff --git a/java.preprocessorbridge/nbproject/project.properties b/java.preprocessorbridge/nbproject/project.properties
--- a/java.preprocessorbridge/nbproject/project.properties
+++ b/java.preprocessorbridge/nbproject/project.properties
@@ -38,6 +38,6 @@
is.autoload=true
javac.compilerargs=-Xlint:unchecked
javac.source=1.7
-spec.version.base=1.40.0
+spec.version.base=1.41.0
javadoc.apichanges=${basedir}/apichanges.xml
diff --git a/java.preprocessorbridge/nbproject/project.xml b/java.preprocessorbridge/nbproject/project.xml
--- a/java.preprocessorbridge/nbproject/project.xml
+++ b/java.preprocessorbridge/nbproject/project.xml
@@ -54,6 +54,15 @@
+ org.netbeans.api.java.classpath
+
+
+
+ 1
+ 1.52
+
+
+
org.netbeans.libs.javacapi
@@ -111,6 +120,7 @@
org.netbeans.modules.groovy.editor
org.netbeans.modules.java.editor
org.netbeans.modules.java.hints
+ org.netbeans.modules.java.j2seproject
org.netbeans.modules.java.source
org.netbeans.modules.java.source.base
org.netbeans.modules.java.sourceui
diff --git a/java.preprocessorbridge/src/org/netbeans/modules/java/preprocessorbridge/api/CompileOnSaveActionQuery.java b/java.preprocessorbridge/src/org/netbeans/modules/java/preprocessorbridge/api/CompileOnSaveActionQuery.java
new file mode 100644
--- /dev/null
+++ b/java.preprocessorbridge/src/org/netbeans/modules/java/preprocessorbridge/api/CompileOnSaveActionQuery.java
@@ -0,0 +1,72 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2016 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.java.preprocessorbridge.api;
+
+import java.net.URL;
+import org.netbeans.api.annotations.common.CheckForNull;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.modules.java.preprocessorbridge.spi.CompileOnSaveAction;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author Tomas Zezula
+ */
+public final class CompileOnSaveActionQuery {
+ private static final Lookup.Result instances
+ = Lookup.getDefault().lookupResult(CompileOnSaveAction.Provider.class);
+
+ private CompileOnSaveActionQuery() {
+ throw new IllegalStateException("No instance allowed."); //NOI18N
+ }
+
+ @CheckForNull
+ public static CompileOnSaveAction getAction(@NonNull final URL sourceRoot) {
+ for (CompileOnSaveAction.Provider p : instances.allInstances()) {
+ final CompileOnSaveAction action = p.forRoot(sourceRoot);
+ if (action != null) {
+ return action;
+ }
+ }
+ return null;
+ }
+}
diff --git a/java.preprocessorbridge/src/org/netbeans/modules/java/preprocessorbridge/spi/CompileOnSaveAction.java b/java.preprocessorbridge/src/org/netbeans/modules/java/preprocessorbridge/spi/CompileOnSaveAction.java
new file mode 100644
--- /dev/null
+++ b/java.preprocessorbridge/src/org/netbeans/modules/java/preprocessorbridge/spi/CompileOnSaveAction.java
@@ -0,0 +1,250 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2016 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.java.preprocessorbridge.spi;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.netbeans.api.annotations.common.CheckForNull;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.annotations.common.NullAllowed;
+import org.netbeans.api.annotations.common.NullUnknown;
+import org.netbeans.api.java.queries.BinaryForSourceQuery;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.BaseUtilities;
+import org.openide.util.Exceptions;
+import org.openide.util.Parameters;
+
+/**
+ * @since 1.41
+ * @author Tomas Zezula
+ */
+public interface CompileOnSaveAction {
+
+ public Boolean performAction (@NonNull final Context ctx) throws IOException;
+ public boolean isUpdateResources();
+ public boolean isUpdateClasses();
+
+ enum Operation {
+ CLEAN,
+ UPDATE,
+ SYNC
+ }
+
+ final class Context {
+ private final Operation operation;
+ private final URL srcRoot;
+ private final boolean isCopyResources;
+ private final boolean isKeepResourcesUpToDate;
+ private final File cacheRoot;
+ private final Iterable extends File> updated;
+ private final Iterable extends File> deleted;
+ private final Object owner;
+ private final Consumer> firer;
+
+ private Context(
+ @NonNull final Operation operation,
+ @NonNull final URL srcRoot,
+ final boolean isCopyResources,
+ final boolean isKeepResourcesUpToDate,
+ @NullAllowed final File cacheRoot,
+ @NullAllowed final Iterable extends File> updated,
+ @NullAllowed final Iterable extends File> deleted,
+ @NullAllowed final Object owner,
+ @NullAllowed final Consumer> firer) {
+ this.operation = operation;
+ this.srcRoot = srcRoot;
+ this.isCopyResources = isCopyResources;
+ this.isKeepResourcesUpToDate = isKeepResourcesUpToDate;
+ this.cacheRoot = cacheRoot;
+ this.updated = updated;
+ this.deleted = deleted;
+ this.owner = owner;
+ this.firer = firer;
+ }
+
+ @NonNull
+ public Operation getOperation() {
+ return operation;
+ }
+
+ @NonNull
+ public Iterable extends File> getUpdated() {
+ if (operation != Operation.UPDATE) {
+ throw new IllegalStateException();
+ }
+ return updated;
+ }
+
+ @NonNull
+ public Iterable extends File> getDeleted() {
+ if (operation != Operation.UPDATE) {
+ throw new IllegalStateException();
+ }
+ return deleted;
+ }
+
+ public boolean isCopyResources() {
+ if (operation == Operation.CLEAN) {
+ throw new IllegalStateException();
+ }
+ return isCopyResources;
+ }
+
+ public boolean isKeepResourcesUpToDate() {
+ if (operation != Operation.SYNC) {
+ throw new IllegalStateException();
+ }
+ return isKeepResourcesUpToDate;
+ }
+
+ @NonNull
+ public URL getSourceRoot() {
+ return srcRoot;
+ }
+
+ @NonNull
+ public File getCacheRoot() {
+ if (operation != Operation.UPDATE) {
+ throw new IllegalStateException();
+ }
+ return cacheRoot;
+ }
+
+ @CheckForNull
+ public File getTarget() {
+ return getTarget(srcRoot);
+ }
+
+ @NonNull
+ public Object getOwner() {
+ if (operation != Operation.SYNC) {
+ throw new IllegalStateException();
+ }
+ return owner;
+ }
+
+ public void filesUpdated(@NonNull final Iterable updatedFiles) {
+ if (firer != null) {
+ firer.accept(updatedFiles);
+ }
+ }
+
+ @NonNull
+ public static Context clean(@NonNull final URL srcRoot) {
+ Parameters.notNull("srcRoot", srcRoot); //NOI18N
+ return new Context(Operation.CLEAN, srcRoot, false, false, null, null, null, null, null);
+ }
+
+ @NonNull
+ public static Context update(
+ @NonNull final URL srcRoot,
+ final boolean isCopyResources,
+ @NonNull final File cacheRoot,
+ @NonNull final Iterable extends File> updated,
+ @NonNull final Iterable extends File> deleted,
+ @NullAllowed final Consumer> firer) {
+ Parameters.notNull("srcRoot", srcRoot); //NOI18N
+ Parameters.notNull("cacheRoot", cacheRoot); //NOI18N
+ Parameters.notNull("updated", updated); //NOI18N
+ Parameters.notNull("deleted", deleted); //NOI18N
+ return new Context(
+ Operation.UPDATE, srcRoot, isCopyResources, false, cacheRoot, updated, deleted, null, firer);
+ }
+
+ @NonNull
+ public static Context sync(
+ @NonNull final URL srcRoot,
+ final boolean isCopyResources,
+ final boolean isKeepResourcesUpToDate,
+ @NonNull final Object owner) {
+ Parameters.notNull("srcRoot", srcRoot); //NOI18N
+ Parameters.notNull("owner", owner); //NOI18N
+ return new Context(
+ Operation.SYNC, srcRoot, isCopyResources, isKeepResourcesUpToDate, null, null, null, owner, null);
+ }
+
+ @CheckForNull
+ public static File getTarget(@NonNull URL srcRoot) {
+ BinaryForSourceQuery.Result binaryRoots = BinaryForSourceQuery.findBinaryRoots(srcRoot);
+
+ File result = null;
+
+ for (URL u : binaryRoots.getRoots()) {
+ assert u != null : "Null in BinaryForSourceQuery.Result.roots: " + binaryRoots; //NOI18N
+ if (u == null) {
+ continue;
+ }
+ File f = FileUtil.archiveOrDirForURL(u);
+
+ try {
+ if (FileUtil.isArchiveFile(BaseUtilities.toURI(f).toURL())) {
+ continue;
+ }
+
+ if (f != null && result != null) {
+ Logger.getLogger(CompileOnSaveAction.class.getName()).log(
+ Level.WARNING,
+ "More than one binary directory for root: {0}",
+ srcRoot.toExternalForm());
+ return null;
+ }
+
+ result = f;
+ } catch (MalformedURLException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+
+ return result;
+ }
+ }
+
+ interface Provider {
+ CompileOnSaveAction forRoot(@NonNull final URL root);
+ }
+}
diff --git a/java.source.ant/antsrc/org/netbeans/modules/java/source/ant/CosUpdated.java b/java.source.ant/antsrc/org/netbeans/modules/java/source/ant/CosUpdated.java
new file mode 100644
--- /dev/null
+++ b/java.source.ant/antsrc/org/netbeans/modules/java/source/ant/CosUpdated.java
@@ -0,0 +1,239 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2016 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.java.source.ant;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Iterator;
+import java.util.Objects;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Reference;
+import org.apache.tools.ant.types.Resource;
+
+/**
+ *
+ * @author Tomas Zezula
+ */
+public final class CosUpdated extends Task {
+ private String id;
+ private File srcdir;
+ private String includes;
+
+ public void setId(final String id) {
+ id.getClass();
+ this.id = id;
+ }
+
+
+ public String getId() {
+ return this.id;
+ }
+
+ public void setSrcdir(final File dir) {
+ this.srcdir = dir;
+ }
+
+ public File getSrcdir() {
+ return this.srcdir;
+ }
+
+ public void setIncludes(final String includes) {
+ this.includes = includes;
+ }
+
+ public String getIncludes() {
+ return this.includes;
+ }
+
+ @Override
+ public void execute() throws BuildException {
+ if (this.id == null || this.id.isEmpty()) {
+ throw new BuildException("The id has to be set."); //NOI18N
+ }
+ if (this.srcdir == null || !this.srcdir.isDirectory()) {
+ throw new BuildException("The srcdir has to point to a directory."); //NOI18N
+ }
+ if (this.includes == null || this.includes.isEmpty()) {
+ throw new BuildException("The includes has to be set."); //NOI18N
+ }
+ final Project prj = getProject();
+ final CosFileSet cfs = new CosFileSet();
+ cfs.setProject(prj);
+ cfs.setDir(this.srcdir);
+ for (String include : includes.split(",")) { //NOI18N
+ include = include.trim();
+ if (!include.isEmpty()) {
+ cfs.createInclude().setName(include);
+ }
+ }
+ prj.addReference(this.id, cfs);
+ }
+
+ private static final class CosFileSet extends FileSet {
+
+ @Override
+ public Iterator iterator() {
+ return new CosFileSetIterator(super.iterator());
+ }
+
+ @Override
+ public boolean isFilesystemOnly() {
+ return false;
+ }
+
+ private static final class CosFileSetIterator implements Iterator {
+
+ private final Iterator delegate;
+
+ CosFileSetIterator(final Iterator delegate) {
+ delegate.getClass();
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return delegate.hasNext();
+ }
+
+ @Override
+ public Resource next() {
+ return new CosResource(delegate.next());
+ }
+ }
+
+ private static final class CosResource extends Resource {
+
+ private final Resource delegate;
+
+ CosResource(final Resource delegate) {
+ delegate.getClass();
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void setRefid(Reference r) {
+ throw tooManyAttributes();
+ }
+
+ @Override
+ public String getName() {
+ final String name = delegate.getName();
+ return name.replace(".sig", ".class"); //NOI18N
+ }
+
+ @Override
+ public boolean isExists() {
+ return delegate.isExists();
+ }
+
+ @Override
+ public long getLastModified() {
+ return delegate.getLastModified();
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return delegate.isDirectory();
+ }
+
+ @Override
+ public long getSize() {
+ return delegate.getSize();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return delegate.getInputStream();
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ return delegate.getOutputStream();
+ }
+
+ @Override
+ public int compareTo(Resource another) {
+ if (another instanceof CosResource) {
+ return delegate.compareTo(((CosResource)another).delegate);
+ } else {
+ return toString().compareTo(String.valueOf(another));
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 71 * hash + Objects.hashCode(this.delegate);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ return Objects.equals(this.delegate, ((CosResource)obj).delegate);
+ }
+
+ @Override
+ public String toString() {
+ return delegate.toString();
+ }
+
+ @Override
+ public boolean isFilesystemOnly() {
+ return false;
+ }
+ }
+ }
+}
diff --git a/java.source.ant/antsrc/org/netbeans/modules/java/source/ant/antlib.xml b/java.source.ant/antsrc/org/netbeans/modules/java/source/ant/antlib.xml
--- a/java.source.ant/antsrc/org/netbeans/modules/java/source/ant/antlib.xml
+++ b/java.source.ant/antsrc/org/netbeans/modules/java/source/ant/antlib.xml
@@ -47,4 +47,5 @@
+
diff --git a/java.source.base/nbproject/project.xml b/java.source.base/nbproject/project.xml
--- a/java.source.base/nbproject/project.xml
+++ b/java.source.base/nbproject/project.xml
@@ -203,7 +203,7 @@
- 1.22
+ 1.41
diff --git a/java.source.base/src/org/netbeans/modules/java/source/indexing/COSSynchronizingIndexer.java b/java.source.base/src/org/netbeans/modules/java/source/indexing/COSSynchronizingIndexer.java
--- a/java.source.base/src/org/netbeans/modules/java/source/indexing/COSSynchronizingIndexer.java
+++ b/java.source.base/src/org/netbeans/modules/java/source/indexing/COSSynchronizingIndexer.java
@@ -54,6 +54,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.modules.java.preprocessorbridge.spi.CompileOnSaveAction;
import org.netbeans.modules.java.source.usages.BuildArtifactMapperImpl;
import org.netbeans.modules.parsing.impl.indexing.IndexerCache;
import org.netbeans.modules.parsing.impl.indexing.IndexerCache.IndexerInfo;
@@ -80,7 +81,7 @@
if (FileUtil.getArchiveFile(rootURL) != null) {
return;
}
- if (!BuildArtifactMapperImpl.isUpdateResources(BuildArtifactMapperImpl.getTargetFolder(rootURL))) {
+ if (!BuildArtifactMapperImpl.isUpdateResources(rootURL)) {
return ;
}
@@ -156,7 +157,11 @@
@Override
public void filesDeleted(Iterable extends Indexable> deleted, Context context) {
- if (BuildArtifactMapperImpl.getTargetFolder(context.getRootURI()) == null) {
+ final File target = CompileOnSaveAction.Context.getTarget(context.getRootURI());
+ if (target == null) {
+ return;
+ }
+ if (!BuildArtifactMapperImpl.isUpdateClasses(context.getRootURI())) {
return ;
}
diff --git a/java.source.base/src/org/netbeans/modules/java/source/usages/BuildArtifactMapperImpl.java b/java.source.base/src/org/netbeans/modules/java/source/usages/BuildArtifactMapperImpl.java
--- a/java.source.base/src/org/netbeans/modules/java/source/usages/BuildArtifactMapperImpl.java
+++ b/java.source.base/src/org/netbeans/modules/java/source/usages/BuildArtifactMapperImpl.java
@@ -68,17 +68,19 @@
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.queries.AnnotationProcessingQuery;
-import org.netbeans.api.java.queries.BinaryForSourceQuery;
-import org.netbeans.api.java.queries.BinaryForSourceQuery.Result;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.netbeans.api.java.source.BuildArtifactMapper.ArtifactsUpdated;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.queries.FileBuiltQuery;
import org.netbeans.api.queries.FileBuiltQuery.Status;
import org.netbeans.api.queries.VisibilityQuery;
+import org.netbeans.modules.java.preprocessorbridge.api.CompileOnSaveActionQuery;
+import org.netbeans.modules.java.preprocessorbridge.spi.CompileOnSaveAction;
import org.netbeans.modules.java.source.indexing.COSSynchronizingIndexer;
import org.netbeans.modules.java.source.indexing.JavaIndex;
import org.netbeans.modules.java.source.parsing.FileObjects;
@@ -90,7 +92,6 @@
import org.netbeans.modules.parsing.spi.indexing.ErrorsCache;
import org.netbeans.spi.queries.FileBuiltQueryImplementation;
import org.openide.filesystems.FileObject;
-import org.openide.filesystems.FileStateInvalidException;
import org.openide.filesystems.FileUtil;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;
@@ -99,6 +100,7 @@
import org.openide.util.BaseUtilities;
import org.openide.util.Lookup;
import org.openide.util.WeakSet;
+import org.openide.util.lookup.ServiceProvider;
/**
*
@@ -175,229 +177,73 @@
}
}
- private static File getTarget(URL source) {
- Result binaryRoots = BinaryForSourceQuery.findBinaryRoots(source);
-
- File result = null;
-
- for (URL u : binaryRoots.getRoots()) {
- assert u != null : "Null in BinaryForSourceQuery.Result.roots: " + binaryRoots; //NOI18N
- if (u == null) {
- continue;
- }
- File f = FileUtil.archiveOrDirForURL(u);
-
- try {
- if (FileUtil.isArchiveFile(BaseUtilities.toURI(f).toURL())) {
- continue;
- }
-
- if (f != null && result != null) {
- Logger.getLogger(BuildArtifactMapperImpl.class.getName()).log(Level.WARNING, "More than one binary directory for root: {0}", source.toExternalForm());
- return null;
- }
-
- result = f;
- } catch (MalformedURLException ex) {
- Exceptions.printStackTrace(ex);
- }
- }
-
- return result;
- }
-
@SuppressWarnings("deprecation")
public static Boolean ensureBuilt(URL sourceRoot, Object context, boolean copyResources, boolean keepResourceUpToDate) throws IOException {
- File targetFolder = getTarget(sourceRoot);
-
- if (targetFolder == null) {
- return null;
+ final CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(sourceRoot);
+ if (a != null) {
+ final CompileOnSaveAction.Context ctx = CompileOnSaveAction.Context.sync(
+ sourceRoot,
+ copyResources,
+ keepResourceUpToDate,
+ context);
+ return a.performAction(ctx);
}
-
- try {
- SourceUtils.waitScanFinished();
- } catch (InterruptedException e) {
- //Not Important
- LOG.log(Level.FINE, null, e);
- return null;
- }
-
- if (JavaIndex.ensureAttributeValue(sourceRoot, DIRTY_ROOT, null)) {
- IndexingManager.getDefault().refreshIndexAndWait(sourceRoot, null);
- }
-
- if (JavaIndex.getAttribute(sourceRoot, DIRTY_ROOT, null) != null) {
- return false;
- }
-
- FileObject[][] sources = new FileObject[1][];
-
- if (!protectAgainstErrors(targetFolder, sources, context)) {
- return false;
- }
-
- File tagFile = new File(targetFolder, TAG_FILE_NAME);
- File tagUpdateResourcesFile = new File(targetFolder, TAG_UPDATE_RESOURCES);
- final boolean forceResourceCopy = copyResources && keepResourceUpToDate && !tagUpdateResourcesFile.exists();
- final boolean cosActive = tagFile.exists();
- if (cosActive && !forceResourceCopy) {
- return true;
- }
-
- if (!cosActive) {
- delete(targetFolder, false/*#161085: cleanCompletely*/);
- }
-
- if (!targetFolder.exists() && !targetFolder.mkdirs()) {
- throw new IOException("Cannot create destination folder: " + targetFolder.getAbsolutePath());
- }
-
- sources(targetFolder, sources);
-
- for (int i = sources[0].length - 1; i>=0; i--) {
- final FileObject sr = sources[0][i];
- if (!cosActive) {
- URL srURL = sr.toURL();
- File index = JavaIndex.getClassFolder(srURL, true);
-
- if (index == null) {
- //#181992: (not nice) ignore the annotation processing target directory:
- if (srURL.equals(AnnotationProcessingQuery.getAnnotationProcessingOptions(sr).sourceOutputDirectory())) {
- continue;
- }
-
- return null;
- }
-
- copyRecursively(index, targetFolder);
- }
-
- if (copyResources) {
- Set javaMimeTypes = COSSynchronizingIndexer.gatherJavaMimeTypes();
- String[] javaMimeTypesArr = javaMimeTypes.toArray(new String[0]);
-
- copyRecursively(sr, targetFolder, javaMimeTypes, javaMimeTypesArr);
- }
- }
-
- if (!cosActive) {
- new FileOutputStream(tagFile).close();
- }
-
- if (keepResourceUpToDate)
- new FileOutputStream(tagUpdateResourcesFile).close();
-
- return true;
+ return null;
}
@SuppressWarnings("deprecation")
public static Boolean clean(URL sourceRoot) throws IOException {
- File targetFolder = getTarget(sourceRoot);
-
- if (targetFolder == null) {
- return null;
+ final CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(sourceRoot);
+ if (a != null) {
+ final CompileOnSaveAction.Context ctx = CompileOnSaveAction.Context.clean(sourceRoot);
+ return a.performAction(ctx);
}
-
- File tagFile = new File(targetFolder, TAG_FILE_NAME);
-
- if (!tagFile.exists()) {
- return null;
- }
-
- try {
- SourceUtils.waitScanFinished();
- } catch (InterruptedException e) {
- //Not Important
- LOG.log(Level.FINE, null, e);
- return false;
- }
-
- delete(targetFolder, false);
- delete(tagFile, true);
-
return null;
}
- public static File getTargetFolder(URL sourceRoot) {
- File targetFolder = getTarget(sourceRoot);
-
- if (targetFolder == null) {
- return null;
- }
-
- if (!new File(targetFolder, TAG_FILE_NAME).exists()) {
- return null;
- }
-
- return targetFolder;
+ public static boolean isUpdateClasses(URL sourceRoot) {
+ final CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(sourceRoot);
+ return a != null ?
+ a.isUpdateClasses():
+ false;
}
- public static boolean isUpdateResources(File targetFolder) {
- return targetFolder != null && new File(targetFolder, TAG_UPDATE_RESOURCES).exists();
+ public static boolean isUpdateResources(URL srcRoot) {
+ final CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(srcRoot);
+ return a != null ?
+ a.isUpdateResources():
+ false;
}
public static void classCacheUpdated(URL sourceRoot, File cacheRoot, Iterable deleted, Iterable updated, boolean resource) {
- if (!deleted.iterator().hasNext() && !updated.iterator().hasNext()) {
- return ;
- }
-
- File targetFolder = getTargetFolder(sourceRoot);
-
- if (targetFolder == null) {
- return ;
- }
-
- if (resource && !isUpdateResources(targetFolder)) {
- return ;
- }
-
- List updatedFiles = new LinkedList();
-
- for (File deletedFile : deleted) {
- final String relPath = relativizeFile(cacheRoot, deletedFile);
- if (relPath == null) {
- throw new IllegalArgumentException (String.format(
- "Deleted file: %s is not under cache root: %s, (normalized file: %s).", //NOI18N
- deletedFile.getAbsolutePath(),
- cacheRoot.getAbsolutePath(),
- FileUtil.normalizeFile(deletedFile).getAbsolutePath()));
- }
- File toDelete = resolveFile(targetFolder, relPath);
-
- toDelete.delete();
- updatedFiles.add(toDelete);
- }
-
- for (File updatedFile : updated) {
- final String relPath = relativizeFile(cacheRoot, updatedFile);
- if (relPath == null) {
- throw new IllegalArgumentException (String.format(
- "Updated file: %s is not under cache root: %s, (normalized file: %s).", //NOI18N
- updatedFile.getAbsolutePath(),
- cacheRoot.getAbsolutePath(),
- FileUtil.normalizeFile(updatedFile).getAbsolutePath()));
- }
- File target = resolveFile(targetFolder, relPath);
-
+ final CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(sourceRoot);
+ if (a != null) {
try {
- copyFile(updatedFile, target);
- updatedFiles.add(target);
+ final CompileOnSaveAction.Context ctx = CompileOnSaveAction.Context.update(
+ sourceRoot,
+ resource,
+ cacheRoot,
+ updated,
+ deleted,
+ (updatedFiles) -> fire(sourceRoot, updatedFiles));
+ a.performAction(ctx);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
-
- if (updatedFiles.size() > 0) {
+ }
+
+ private static void fire(
+ @NonNull final URL sourceRoot,
+ @NonNull final Iterable updatedFiles) {
+ if (updatedFiles.iterator().hasNext()) {
Set listeners;
-
synchronized (BuildArtifactMapperImpl.class) {
listeners = source2Listener.get(sourceRoot);
-
if (listeners != null) {
- listeners = new HashSet(listeners);
+ listeners = new HashSet<>(listeners);
}
}
-
if (listeners != null) {
for (ArtifactsUpdated listener : listeners) {
listener.artifactsUpdated(updatedFiles);
@@ -405,7 +251,7 @@
}
}
}
-
+
private static void copyFile(File updatedFile, File target) throws IOException {
final File parent = target.getParentFile();
if (parent != null && !parent.exists()) {
@@ -692,7 +538,7 @@
return delegate;
}
- File target = getTarget(owner.getURL());
+ File target = CompileOnSaveAction.Context.getTarget(owner.toURL());
File tagFile = FileUtil.normalizeFile(new File(target, TAG_FILE_NAME));
synchronized(this) {
@@ -713,10 +559,7 @@
}
return result;
- }
- } catch (FileStateInvalidException ex) {
- Exceptions.printStackTrace(ex);
- return null;
+ }
} finally {
recursive.remove();
}
@@ -794,5 +637,232 @@
}
});
}
+ }
+
+ private static final class DefaultCompileOnSaveAction implements CompileOnSaveAction {
+ private final URL root;
+
+ DefaultCompileOnSaveAction(@NonNull final URL root) {
+ this.root = root;
+ }
+
+ @Override
+ public boolean isUpdateClasses() {
+ return isUpdateClasses(CompileOnSaveAction.Context.getTarget(root));
+ }
+
+ @Override
+ public boolean isUpdateResources() {
+ return isUpdateResources(CompileOnSaveAction.Context.getTarget(root));
+ }
+
+ @Override
+ public Boolean performAction(@NonNull final Context ctx) throws IOException {
+ assert root.equals(ctx.getSourceRoot());
+ switch (ctx.getOperation()) {
+ case CLEAN:
+ return performClean(ctx);
+ case SYNC:
+ return performSync(ctx);
+ case UPDATE:
+ return performUpdate(ctx);
+ default:
+ } throw new IllegalArgumentException(String.valueOf(ctx.getOperation()));
+ }
+
+ private Boolean performClean(@NonNull final Context ctx) throws IOException {
+ final File targetFolder = ctx.getTarget();
+
+ if (targetFolder == null) {
+ return null;
+ }
+
+ File tagFile = new File(targetFolder, TAG_FILE_NAME);
+
+ if (!tagFile.exists()) {
+ return null;
+ }
+
+ try {
+ SourceUtils.waitScanFinished();
+ } catch (InterruptedException e) {
+ //Not Important
+ LOG.log(Level.FINE, null, e);
+ return false;
+ }
+
+ delete(targetFolder, false);
+ delete(tagFile, true);
+
+ return null;
+ }
+
+ private Boolean performSync(@NonNull final Context ctx) throws IOException {
+ final URL sourceRoot = ctx.getSourceRoot();
+ final File targetFolder = ctx.getTarget();
+ final boolean copyResources = ctx.isCopyResources();
+ final boolean keepResourceUpToDate = ctx.isKeepResourcesUpToDate();
+ final Object context = ctx.getOwner();
+
+ if (targetFolder == null) {
+ return null;
+ }
+
+ try {
+ SourceUtils.waitScanFinished();
+ } catch (InterruptedException e) {
+ //Not Important
+ LOG.log(Level.FINE, null, e);
+ return null;
+ }
+
+ if (JavaIndex.ensureAttributeValue(sourceRoot, DIRTY_ROOT, null)) {
+ IndexingManager.getDefault().refreshIndexAndWait(sourceRoot, null);
+ }
+
+ if (JavaIndex.getAttribute(sourceRoot, DIRTY_ROOT, null) != null) {
+ return false;
+ }
+
+ FileObject[][] sources = new FileObject[1][];
+
+ if (!protectAgainstErrors(targetFolder, sources, context)) {
+ return false;
+ }
+
+ File tagFile = new File(targetFolder, TAG_FILE_NAME);
+ File tagUpdateResourcesFile = new File(targetFolder, TAG_UPDATE_RESOURCES);
+ final boolean forceResourceCopy = copyResources && keepResourceUpToDate && !tagUpdateResourcesFile.exists();
+ final boolean cosActive = tagFile.exists();
+ if (cosActive && !forceResourceCopy) {
+ return true;
+ }
+
+ if (!cosActive) {
+ delete(targetFolder, false/*#161085: cleanCompletely*/);
+ }
+
+ if (!targetFolder.exists() && !targetFolder.mkdirs()) {
+ throw new IOException("Cannot create destination folder: " + targetFolder.getAbsolutePath());
+ }
+
+ sources(targetFolder, sources);
+
+ for (int i = sources[0].length - 1; i>=0; i--) {
+ final FileObject sr = sources[0][i];
+ if (!cosActive) {
+ URL srURL = sr.toURL();
+ File index = JavaIndex.getClassFolder(srURL, true);
+
+ if (index == null) {
+ //#181992: (not nice) ignore the annotation processing target directory:
+ if (srURL.equals(AnnotationProcessingQuery.getAnnotationProcessingOptions(sr).sourceOutputDirectory())) {
+ continue;
+ }
+
+ return null;
+ }
+
+ copyRecursively(index, targetFolder);
+ }
+
+ if (copyResources) {
+ Set javaMimeTypes = COSSynchronizingIndexer.gatherJavaMimeTypes();
+ String[] javaMimeTypesArr = javaMimeTypes.toArray(new String[0]);
+
+ copyRecursively(sr, targetFolder, javaMimeTypes, javaMimeTypesArr);
+ }
+ }
+
+ if (!cosActive) {
+ new FileOutputStream(tagFile).close();
+ }
+
+ if (keepResourceUpToDate)
+ new FileOutputStream(tagUpdateResourcesFile).close();
+
+ return true;
+ }
+
+ private Boolean performUpdate(@NonNull final Context ctx) throws IOException {
+ final Iterable extends File> deleted = ctx.getDeleted();
+ final Iterable extends File> updated = ctx.getUpdated();
+ final boolean resource = ctx.isCopyResources();
+ final File cacheRoot = ctx.getCacheRoot();
+ if (!deleted.iterator().hasNext() && !updated.iterator().hasNext()) {
+ return null;
+ }
+ File targetFolder = ctx.getTarget();
+ if (targetFolder == null) {
+ return null;
+ }
+ if (!isUpdateClasses(targetFolder)) {
+ return null;
+ }
+
+ if (resource && !isUpdateResources(targetFolder)) {
+ return null;
+ }
+
+ List updatedFiles = new LinkedList<>();
+
+ for (File deletedFile : deleted) {
+ final String relPath = relativizeFile(cacheRoot, deletedFile);
+ if (relPath == null) {
+ throw new IllegalArgumentException (String.format(
+ "Deleted file: %s is not under cache root: %s, (normalized file: %s).", //NOI18N
+ deletedFile.getAbsolutePath(),
+ cacheRoot.getAbsolutePath(),
+ FileUtil.normalizeFile(deletedFile).getAbsolutePath()));
+ }
+ File toDelete = resolveFile(targetFolder, relPath);
+
+ toDelete.delete();
+ updatedFiles.add(toDelete);
+ }
+
+ for (File updatedFile : updated) {
+ final String relPath = relativizeFile(cacheRoot, updatedFile);
+ if (relPath == null) {
+ throw new IllegalArgumentException (String.format(
+ "Updated file: %s is not under cache root: %s, (normalized file: %s).", //NOI18N
+ updatedFile.getAbsolutePath(),
+ cacheRoot.getAbsolutePath(),
+ FileUtil.normalizeFile(updatedFile).getAbsolutePath()));
+ }
+ File target = resolveFile(targetFolder, relPath);
+
+ try {
+ copyFile(updatedFile, target);
+ updatedFiles.add(target);
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+ ctx.filesUpdated(updatedFiles);
+ return true;
+ }
+
+ private boolean isUpdateClasses(@NullAllowed final File targetFolder) {
+ if (targetFolder == null) {
+ return false;
+ }
+ return new File(targetFolder, TAG_FILE_NAME).exists();
+ }
+
+ private boolean isUpdateResources(@NullAllowed final File targetFolder) {
+ if (targetFolder == null) {
+ return false;
+ }
+ return new File(targetFolder, TAG_UPDATE_RESOURCES).exists();
+ }
+ }
+
+ @ServiceProvider(service = CompileOnSaveAction.Provider.class, position = Integer.MAX_VALUE)
+ public static final class Provider implements CompileOnSaveAction.Provider {
+ @Override
+ public CompileOnSaveAction forRoot(@NonNull final URL root) {
+ return new DefaultCompileOnSaveAction(root);
+ }
}
}