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 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 updated = ctx.getUpdated(); + final Iterable 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 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 updated; + private final Iterable 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 updated, + @NullAllowed final Iterable 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 getUpdated() { + if (operation != Operation.UPDATE) { + throw new IllegalStateException(); + } + return updated; + } + + @NonNull + public Iterable 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 updated, + @NonNull final Iterable 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 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 deleted = ctx.getDeleted(); + final Iterable 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); + } } }