This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 252595
Collapse All | Expand All

(-)a/java.j2seproject/nbproject/project.xml (+8 lines)
Lines 163-168 Link Here
163
                    </run-dependency>
163
                    </run-dependency>
164
                </dependency>
164
                </dependency>
165
                <dependency>
165
                <dependency>
166
                    <code-name-base>org.netbeans.modules.java.preprocessorbridge</code-name-base>
167
                    <build-prerequisite/>
168
                    <compile-dependency/>
169
                    <run-dependency>
170
                        <specification-version>1.41</specification-version>
171
                    </run-dependency>
172
                </dependency>
173
                <dependency>
166
                    <code-name-base>org.netbeans.modules.java.project</code-name-base>
174
                    <code-name-base>org.netbeans.modules.java.project</code-name-base>
167
                    <build-prerequisite/>
175
                    <build-prerequisite/>
168
                    <compile-dependency/>
176
                    <compile-dependency/>
(-)a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEActionProvider.java (-21 / +239 lines)
Lines 48-54 Link Here
48
import java.beans.PropertyChangeListener;
48
import java.beans.PropertyChangeListener;
49
import java.io.File;
49
import java.io.File;
50
import java.io.IOException;
50
import java.io.IOException;
51
import java.io.InputStream;
52
import java.io.OutputStream;
53
import java.lang.ref.Reference;
51
import java.lang.ref.WeakReference;
54
import java.lang.ref.WeakReference;
55
import java.net.URISyntaxException;
52
import java.net.URL;
56
import java.net.URL;
53
import java.util.Arrays;
57
import java.util.Arrays;
54
import java.util.Collections;
58
import java.util.Collections;
Lines 57-70 Link Here
57
import java.util.Map;
61
import java.util.Map;
58
import java.util.Properties;
62
import java.util.Properties;
59
import java.util.Set;
63
import java.util.Set;
64
import java.util.WeakHashMap;
65
import java.util.function.Function;
60
import java.util.logging.Level;
66
import java.util.logging.Level;
61
import java.util.logging.Logger;
67
import java.util.logging.Logger;
68
import javax.swing.event.ChangeListener;
62
import org.apache.tools.ant.module.api.support.ActionUtils;
69
import org.apache.tools.ant.module.api.support.ActionUtils;
63
import org.netbeans.api.annotations.common.CheckForNull;
70
import org.netbeans.api.annotations.common.CheckForNull;
64
import org.netbeans.api.annotations.common.NonNull;
71
import org.netbeans.api.annotations.common.NonNull;
72
import org.netbeans.api.annotations.common.StaticResource;
65
import org.netbeans.api.java.classpath.ClassPath;
73
import org.netbeans.api.java.classpath.ClassPath;
66
import org.netbeans.api.java.project.JavaProjectConstants;
74
import org.netbeans.api.java.project.JavaProjectConstants;
67
import org.netbeans.api.java.source.BuildArtifactMapper;
75
import org.netbeans.api.java.source.BuildArtifactMapper;
76
import org.netbeans.api.project.FileOwnerQuery;
77
import org.netbeans.api.project.Project;
68
import org.netbeans.modules.java.api.common.SourceRoots;
78
import org.netbeans.modules.java.api.common.SourceRoots;
69
import org.netbeans.modules.java.api.common.ant.UpdateHelper;
79
import org.netbeans.modules.java.api.common.ant.UpdateHelper;
70
import org.netbeans.modules.java.api.common.project.ProjectProperties;
80
import org.netbeans.modules.java.api.common.project.ProjectProperties;
Lines 72-87 Link Here
72
import org.netbeans.modules.java.api.common.project.BaseActionProvider.Callback3;
82
import org.netbeans.modules.java.api.common.project.BaseActionProvider.Callback3;
73
import org.netbeans.modules.java.api.common.project.ProjectConfigurations;
83
import org.netbeans.modules.java.api.common.project.ProjectConfigurations;
74
import org.netbeans.modules.java.j2seproject.api.J2SEBuildPropertiesProvider;
84
import org.netbeans.modules.java.j2seproject.api.J2SEBuildPropertiesProvider;
85
import org.netbeans.modules.java.preprocessorbridge.spi.CompileOnSaveAction;
75
import org.netbeans.spi.project.ActionProvider;
86
import org.netbeans.spi.project.ActionProvider;
76
import org.netbeans.spi.project.LookupProvider;
87
import org.netbeans.spi.project.LookupProvider;
77
import org.netbeans.spi.project.ProjectServiceProvider;
88
import org.netbeans.spi.project.ProjectServiceProvider;
78
import org.netbeans.spi.project.SingleMethod;
89
import org.netbeans.spi.project.SingleMethod;
79
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
90
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
91
import org.openide.filesystems.FileLock;
80
import org.openide.filesystems.FileObject;
92
import org.openide.filesystems.FileObject;
81
import org.openide.filesystems.FileUtil;
93
import org.openide.filesystems.FileUtil;
94
import org.openide.modules.Places;
95
import org.openide.util.BaseUtilities;
96
import org.openide.util.ChangeSupport;
97
import org.openide.util.Exceptions;
82
import org.openide.util.Lookup;
98
import org.openide.util.Lookup;
99
import org.openide.util.Pair;
83
import org.openide.util.Parameters;
100
import org.openide.util.Parameters;
84
import org.openide.util.WeakListeners;
101
import org.openide.util.WeakListeners;
102
import org.openide.util.lookup.ServiceProvider;
85
103
86
/** Action provider of the J2SE project. This is the place where to do
104
/** Action provider of the J2SE project. This is the place where to do
87
 * strange things to J2SE actions. E.g. compile-single.
105
 * strange things to J2SE actions. E.g. compile-single.
Lines 341-348 Link Here
341
    }
359
    }
342
360
343
    private static final class CosAction implements BuildArtifactMapper.ArtifactsUpdated,
361
    private static final class CosAction implements BuildArtifactMapper.ArtifactsUpdated,
344
            PropertyChangeListener {
362
            CompileOnSaveAction, PropertyChangeListener {
363
        private static Map<Project,Reference<CosAction>> instances = new WeakHashMap<>();
345
        private static final String COS_UPDATED = "$cos.update";    //NOI18N
364
        private static final String COS_UPDATED = "$cos.update";    //NOI18N
365
        private static final String COS_CUSTOM = "$cos.update.custom";    //NOI18N
366
        private static final String PROP_TARGET = "cos.update.target.internal";  //NOI18N
367
        private static final String PROP_SCRIPT = "cos.update.script.internal";  //NOI18N
368
        private static final String PROP_SRCDIR = "cos.src.dir.internal";   //NOI18N
369
        private static final String PROP_INCLUDES ="cos.includes.internal"; //NOI18N
370
        private static final String SNIPPETS = "executor-snippets"; //NOI18N
371
        private static final String SCRIPT = "cos-update.xml"; //NOI18N
372
        private static final String TARGET = "cos-update-internal"; //NOI18N
373
        private static final String SCRIPT_TEMPLATE = "/org/netbeans/modules/java/j2seproject/resources/cos-update-snippet.xml"; //NOI18N
346
        private static final Object NONE = new Object();
374
        private static final Object NONE = new Object();
347
        private final J2SEActionProvider owner;
375
        private final J2SEActionProvider owner;
348
        private final PropertyEvaluator eval;
376
        private final PropertyEvaluator eval;
Lines 350-356 Link Here
350
        private final SourceRoots tests;
378
        private final SourceRoots tests;
351
        private final BuildArtifactMapper mapper;
379
        private final BuildArtifactMapper mapper;
352
        private final Map</*@GuardedBy("this")*/URL,BuildArtifactMapper.ArtifactsUpdated> currentListeners;
380
        private final Map</*@GuardedBy("this")*/URL,BuildArtifactMapper.ArtifactsUpdated> currentListeners;
381
        private final ChangeSupport cs;
353
        private volatile Object targetCache;
382
        private volatile Object targetCache;
383
        private volatile Boolean enabledCache;
354
384
355
        private CosAction(
385
        private CosAction(
356
                @NonNull final J2SEActionProvider owner,
386
                @NonNull final J2SEActionProvider owner,
Lines 363-394 Link Here
363
            this.tests = tests;
393
            this.tests = tests;
364
            this.mapper = new BuildArtifactMapper();
394
            this.mapper = new BuildArtifactMapper();
365
            this.currentListeners = new HashMap<>();
395
            this.currentListeners = new HashMap<>();
396
            this.cs = new ChangeSupport(this);
366
            this.eval.addPropertyChangeListener(WeakListeners.propertyChange(this, this.eval));
397
            this.eval.addPropertyChangeListener(WeakListeners.propertyChange(this, this.eval));
367
            this.src.addPropertyChangeListener(WeakListeners.propertyChange(this, this.src));
398
            this.src.addPropertyChangeListener(WeakListeners.propertyChange(this, this.src));
368
            this.tests.addPropertyChangeListener(WeakListeners.propertyChange(this, this.tests));
399
            this.tests.addPropertyChangeListener(WeakListeners.propertyChange(this, this.tests));
369
            updateRootsListeners();
400
            updateRootsListeners();
401
            instances.put(owner.getProject(), new WeakReference<>(this));
370
        }
402
        }
371
403
372
        @Override
404
        @Override
405
        public boolean isEnabled() {
406
            return getTarget() != null && isCustomUpdate();
407
        }
408
409
        @Override
410
        public boolean isUpdateClasses() {
411
            return isEnabled();
412
        }
413
414
        @Override
415
        public boolean isUpdateResources() {
416
            return isEnabled();
417
        }
418
419
        @Override
420
        public Boolean performAction(Context ctx) throws IOException {
421
            switch (ctx.getOperation()) {
422
                case UPDATE:
423
                    return performUpdate(ctx);
424
                case CLEAN:
425
                    return performClean(ctx);
426
                case SYNC:
427
                    return performSync(ctx);
428
                default:
429
                    throw new IllegalArgumentException(String.valueOf(ctx.getOperation()));                 
430
            }
431
        }               
432
433
        @Override
373
        public void artifactsUpdated(@NonNull final Iterable<File> artifacts) {
434
        public void artifactsUpdated(@NonNull final Iterable<File> artifacts) {
374
            final String target = getTarget();
435
            if (!isCustomUpdate()) {
375
            if (target != null) {
436
                final String target = getTarget();
376
                final FileObject buildXml = owner.findBuildXml();
437
                if (target != null) {
377
                if (buildXml != null) {
438
                    final FileObject buildXml = owner.findBuildXml();
378
                    try {
439
                    if (buildXml != null) {
379
                        ActionUtils.runTarget(
440
                        try {
380
                                buildXml,
441
                                ActionUtils.runTarget(
381
                                new String[] {target},
442
                                    buildXml,
382
                                null,
443
                                    new String[] {target},
383
                                null);
444
                                    null,
384
                    } catch (IOException ioe) {
445
                                        null);
385
                        LOG.log(
446
                        } catch (IOException ioe) {
386
                                Level.WARNING,
447
                            LOG.log(
387
                                "Cannot execute pos compile on save target: {0} in: {1}",   //NOI18N
448
                                    Level.WARNING,
388
                                new Object[]{
449
                                    "Cannot execute pos compile on save target: {0} in: {1}",   //NOI18N
389
                                    target,
450
                                    new Object[]{
390
                                    FileUtil.getFileDisplayName(buildXml)
451
                                        target,
391
                                });
452
                                        FileUtil.getFileDisplayName(buildXml)
453
                                    });
454
                        }
392
                    }
455
                    }
393
                }
456
                }
394
            }
457
            }
Lines 397-409 Link Here
397
        @Override
460
        @Override
398
        public void propertyChange(@NonNull final PropertyChangeEvent evt) {
461
        public void propertyChange(@NonNull final PropertyChangeEvent evt) {
399
            final String name = evt.getPropertyName();
462
            final String name = evt.getPropertyName();
400
            if (name == null || COS_UPDATED.equals(name)) {
463
            if (name == null) {
401
                targetCache = null;
464
                targetCache = null;
402
            } else if (SourceRoots.PROP_ROOTS.equals(name)) {
465
                enabledCache = null;
466
                cs.fireChange();
467
            } else if (COS_UPDATED.equals(name)) {
468
                targetCache = null;
469
                cs.fireChange();
470
            } else if (COS_CUSTOM.equals(name)) {
471
                enabledCache = null;
472
                cs.fireChange();
473
            }else if (SourceRoots.PROP_ROOTS.equals(name)) {
403
                updateRootsListeners();
474
                updateRootsListeners();
404
            }
475
            }
405
        }
476
        }
406
477
478
        @Override
479
        public void addChangeListener(@NonNull final ChangeListener listener) {
480
            cs.addChangeListener(listener);
481
        }
482
483
        @Override
484
        public void removeChangeListner(@NonNull final ChangeListener listener) {
485
            cs.removeChangeListener(listener);
486
        }               
487
407
        private void updateRootsListeners() {
488
        private void updateRootsListeners() {
408
            final Set<URL> newRoots = new HashSet<>();
489
            final Set<URL> newRoots = new HashSet<>();
409
            Collections.addAll(newRoots, this.src.getRootURLs());
490
            Collections.addAll(newRoots, this.src.getRootURLs());
Lines 440-445 Link Here
440
                    (String) target :
521
                    (String) target :
441
                    null;
522
                    null;
442
        }
523
        }
524
        
525
        private boolean isCustomUpdate() {
526
            Boolean res = enabledCache;
527
            if (res == null) {
528
                final String val = eval.getProperty(COS_CUSTOM);
529
                res = enabledCache = Boolean.valueOf(val);
530
            }
531
            return res;
532
        }
533
        
534
        @CheckForNull
535
        private Boolean performUpdate(@NonNull final Context ctx) {
536
            final String target = getTarget();
537
            if (target != null) {
538
                final FileObject buildXml = owner.findBuildXml();
539
                if (buildXml != null) {
540
                    try {
541
                        final FileObject cosScript = getCosScript();
542
                        final Iterable<? extends File> updated = ctx.getUpdated();
543
                        final Iterable<? extends File> deleted = ctx.getDeleted();
544
                        final File root = ctx.isCopyResources() ?
545
                                BaseUtilities.toFile(ctx.getSourceRoot().toURI()) :
546
                                ctx.getCacheRoot();
547
                        final String includes = createIncludes(root, updated);
548
                        if (includes != null) {
549
                            final Properties props = new Properties();
550
                            props.setProperty(PROP_TARGET, target);
551
                            props.setProperty(PROP_SCRIPT, FileUtil.toFile(buildXml).getAbsolutePath());
552
                            props.setProperty(PROP_SRCDIR, root.getAbsolutePath());
553
                            props.setProperty(PROP_INCLUDES, includes);
554
                            ActionUtils.runTarget(
555
                                    cosScript,
556
                                    new String[] {TARGET},
557
                                    props,
558
                                    null);
559
                        } else {
560
                            LOG.warning("BuildArtifactMapper artifacts do not provide attributes.");    //NOI18N
561
                        }
562
                    } catch (IOException | URISyntaxException e) {
563
                        LOG.log(
564
                                Level.WARNING,
565
                                "Cannot execute update targer on save target: {0} in: {1} due to: {2}",   //NOI18N
566
                                new Object[]{
567
                                    target,
568
                                    FileUtil.getFileDisplayName(buildXml),
569
                                    e.getMessage()
570
                                });
571
                    }
572
                }
573
            }
574
            return true;
575
        }
576
        
577
        @CheckForNull
578
        private Boolean performClean(@NonNull final Context ctx) {
579
            //Not sure what to do
580
            return null;
581
        }
582
        
583
        @CheckForNull
584
        private Boolean performSync(@NonNull final Context ctx) {
585
            //Not sure what to do
586
            return null;
587
        }
588
        
589
        @NonNull
590
        private FileObject getCosScript() throws IOException {
591
            final FileObject snippets = FileUtil.createFolder(
592
                    Places.getCacheSubdirectory(SNIPPETS));
593
            FileObject cosScript = snippets.getFileObject(SCRIPT);
594
            if (cosScript == null) {
595
                cosScript = FileUtil.createData(snippets, SCRIPT);
596
                final FileLock lock = cosScript.lock();
597
                try (InputStream in = getClass().getResourceAsStream(SCRIPT_TEMPLATE);
598
                        OutputStream out = cosScript.getOutputStream(lock)) {
599
                    FileUtil.copy(in, out);
600
                } finally {
601
                    lock.releaseLock();
602
                }
603
            }
604
            return cosScript;
605
        }        
606
        
607
        @CheckForNull
608
        private static String createIncludes(
609
                @NonNull final File root,
610
                @NonNull final Iterable<? extends File> artifacts) {
611
            final StringBuilder include = new StringBuilder();
612
            for (File f : artifacts) {
613
                if (include.length() > 0) {
614
                    include.append(','); //NOI18N
615
                }
616
                include.append(relativize(f,root));
617
            }
618
            return include.length() == 0 ?
619
                    null :
620
                    include.toString();
621
        }
622
        
623
        private static String relativize(
624
                @NonNull final File file,
625
                @NonNull final File folder) {
626
            final String folderPath = folder.getAbsolutePath();
627
            int start = folderPath.length();
628
            if (!folderPath.endsWith(File.separator)) {
629
                start++;
630
            }
631
            return file.getAbsolutePath().substring(start);
632
        }                        
633
        
634
        @CheckForNull
635
        static CosAction getInstance(@NonNull final Project p) {
636
            final Reference<CosAction> r = instances.get(p);
637
            return r != null ?
638
                    r.get() :
639
                    null;
640
        }
443
641
444
        private static final class WeakArtifactUpdated extends WeakReference<BuildArtifactMapper.ArtifactsUpdated>
642
        private static final class WeakArtifactUpdated extends WeakReference<BuildArtifactMapper.ArtifactsUpdated>
445
                implements BuildArtifactMapper.ArtifactsUpdated, Runnable {
643
                implements BuildArtifactMapper.ArtifactsUpdated, Runnable {
Lines 473-476 Link Here
473
            }
671
            }
474
        }
672
        }
475
    }
673
    }
674
    
675
    @ServiceProvider(service = CompileOnSaveAction.Provider.class, position = 10_000)
676
    public static final class Provider implements CompileOnSaveAction.Provider {
677
678
        @Override
679
        public CompileOnSaveAction forRoot(URL root) {
680
            try {
681
                final Project p = FileOwnerQuery.getOwner(root.toURI());
682
                if (p != null) {
683
                    p.getLookup().lookup(ActionProvider.class).getSupportedActions();   //Force initialization
684
                    final CosAction action = CosAction.getInstance(p);
685
                    return action;
686
                }
687
            } catch (URISyntaxException e) {
688
                Exceptions.printStackTrace(e);
689
            }
690
            return null;
691
        }
692
        
693
    }
476
}
694
}
(-)a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/resources/cos-update-snippet.xml (+51 lines)
Line 0 Link Here
1
<?xml version="1.0" encoding="UTF-8"?>
2
<!--
3
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
5
Copyright 2010 Oracle and/or its affiliates. All rights reserved.
6
7
Oracle and Java are registered trademarks of Oracle and/or its affiliates.
8
Other names may be trademarks of their respective owners.
9
10
11
The contents of this file are subject to the terms of either the GNU
12
General Public License Version 2 only ("GPL") or the Common
13
Development and Distribution License("CDDL") (collectively, the
14
"License"). You may not use this file except in compliance with the
15
License. You can obtain a copy of the License at
16
http://www.netbeans.org/cddl-gplv2.html
17
or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
18
specific language governing permissions and limitations under the
19
License.  When distributing the software, include this License Header
20
Notice in each file and include the License file at
21
nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
22
particular file as subject to the "Classpath" exception as provided
23
by Oracle in the GPL Version 2 section of the License file that
24
accompanied this code. If applicable, add the following below the
25
License Header, with the fields enclosed by brackets [] replaced by
26
your own identifying information:
27
"Portions Copyrighted [year] [name of copyright owner]"
28
29
Contributor(s):
30
31
The Original Software is NetBeans. The Initial Developer of the Original
32
Software is Sun Microsystems, Inc. Portions Copyright 2008 Sun
33
Microsystems, Inc. All Rights Reserved.
34
35
If you wish your version of this file to be governed by only the CDDL
36
or only the GPL Version 2, indicate your decision by adding
37
"[Contributor] elects to include this software in this distribution
38
under the [CDDL or GPL Version 2] license." If you do not indicate a
39
single choice of license, a recipient has the option to distribute
40
your version of this file under either the CDDL, the GPL Version 2 or
41
to extend the choice of license to its licensees as provided above.
42
However, if you add GPL Version 2 code and therefore, elected the GPL
43
Version 2 license, then the option applies only if the new code is
44
made subject to such option by the copyright holder.
45
-->
46
<project name="cos-update" default="cos-update-internal">
47
    <target name="cos-update-internal">
48
        <cos-updated id="cos.updated.files" srcdir="${cos.src.dir.internal}" includes="${cos.includes.internal}"/>
49
        <ant antfile="${cos.update.script.internal}" target="${cos.update.target.internal}" useNativeBasedir="true" inheritRefs="true"/>
50
    </target>
51
</project>
(-)a/java.preprocessorbridge/nbproject/project.properties (-2 / +2 lines)
Lines 37-43 Link Here
37
# Contributor(s):
37
# Contributor(s):
38
is.autoload=true
38
is.autoload=true
39
javac.compilerargs=-Xlint:unchecked
39
javac.compilerargs=-Xlint:unchecked
40
javac.source=1.7
40
javac.source=1.8
41
spec.version.base=1.40.0
41
spec.version.base=1.41.0
42
javadoc.apichanges=${basedir}/apichanges.xml
42
javadoc.apichanges=${basedir}/apichanges.xml
43
43
(-)a/java.preprocessorbridge/nbproject/project.xml (+37 lines)
Lines 54-59 Link Here
54
                    </run-dependency>
54
                    </run-dependency>
55
                </dependency>
55
                </dependency>
56
                <dependency>
56
                <dependency>
57
                    <code-name-base>org.netbeans.api.java.classpath</code-name-base>
58
                    <build-prerequisite/>
59
                    <compile-dependency/>
60
                    <run-dependency>
61
                        <release-version>1</release-version>
62
                        <specification-version>1.52</specification-version>
63
                    </run-dependency>
64
                </dependency>
65
                <dependency>
57
                    <code-name-base>org.netbeans.libs.javacapi</code-name-base>
66
                    <code-name-base>org.netbeans.libs.javacapi</code-name-base>
58
                    <build-prerequisite/>
67
                    <build-prerequisite/>
59
                    <compile-dependency/>
68
                    <compile-dependency/>
Lines 103-108 Link Here
103
                    </run-dependency>
112
                    </run-dependency>
104
                </dependency>
113
                </dependency>
105
            </module-dependencies>
114
            </module-dependencies>
115
            <test-dependencies>
116
                <test-type>
117
                    <name>unit</name>
118
                    <test-dependency>
119
                        <code-name-base>org.netbeans.insane</code-name-base>
120
                        <compile-dependency/>
121
                    </test-dependency>
122
                    <test-dependency>
123
                        <code-name-base>org.netbeans.libs.junit4</code-name-base>
124
                        <compile-dependency/>
125
                    </test-dependency>
126
                    <test-dependency>
127
                        <code-name-base>org.netbeans.modules.nbjunit</code-name-base>
128
                        <compile-dependency/>
129
                    </test-dependency>
130
                    <test-dependency>
131
                        <code-name-base>org.openide.util</code-name-base>
132
                        <compile-dependency/>
133
                        <test/>
134
                    </test-dependency>
135
                    <test-dependency>
136
                        <code-name-base>org.openide.util.lookup</code-name-base>
137
                        <compile-dependency/>
138
                        <test/>
139
                    </test-dependency>
140
                </test-type>
141
            </test-dependencies>
106
            <friend-packages>
142
            <friend-packages>
107
                <friend>com.ptc.rbinfohandler</friend>
143
                <friend>com.ptc.rbinfohandler</friend>
108
                <friend>org.netbeans.modules.debugger.jpda.projects</friend>
144
                <friend>org.netbeans.modules.debugger.jpda.projects</friend>
Lines 111-116 Link Here
111
                <friend>org.netbeans.modules.groovy.editor</friend>
147
                <friend>org.netbeans.modules.groovy.editor</friend>
112
                <friend>org.netbeans.modules.java.editor</friend>
148
                <friend>org.netbeans.modules.java.editor</friend>
113
                <friend>org.netbeans.modules.java.hints</friend>
149
                <friend>org.netbeans.modules.java.hints</friend>
150
                <friend>org.netbeans.modules.java.j2seproject</friend>
114
                <friend>org.netbeans.modules.java.source</friend>
151
                <friend>org.netbeans.modules.java.source</friend>
115
                <friend>org.netbeans.modules.java.source.base</friend>
152
                <friend>org.netbeans.modules.java.source.base</friend>
116
                <friend>org.netbeans.modules.java.sourceui</friend>
153
                <friend>org.netbeans.modules.java.sourceui</friend>
(-)a/java.preprocessorbridge/src/org/netbeans/modules/java/preprocessorbridge/api/CompileOnSaveActionQuery.java (+218 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2016 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.java.preprocessorbridge.api;
43
44
import java.io.IOException;
45
import java.lang.ref.Reference;
46
import java.lang.ref.WeakReference;
47
import java.net.URL;
48
import java.util.Collection;
49
import java.util.Map;
50
import java.util.WeakHashMap;
51
import java.util.concurrent.atomic.AtomicReference;
52
import java.util.function.Predicate;
53
import java.util.stream.Collectors;
54
import java.util.stream.Stream;
55
import javax.swing.event.ChangeEvent;
56
import javax.swing.event.ChangeListener;
57
import org.netbeans.api.annotations.common.CheckForNull;
58
import org.netbeans.api.annotations.common.NonNull;
59
import org.netbeans.modules.java.preprocessorbridge.spi.CompileOnSaveAction;
60
import org.openide.util.ChangeSupport;
61
import org.openide.util.Lookup;
62
import org.openide.util.LookupEvent;
63
import org.openide.util.LookupListener;
64
import org.openide.util.WeakListeners;
65
66
/**
67
 * Finds Compile On Save performer for given source root.
68
 * @author Tomas Zezula
69
 * @since 1.41
70
 */
71
public final class CompileOnSaveActionQuery {
72
    private static final Lookup.Result<CompileOnSaveAction.Provider> instances
73
            = Lookup.getDefault().lookupResult(CompileOnSaveAction.Provider.class);
74
    //Normalization Cache
75
    //@GuardedBy("u2a")
76
    private static final Map<URL,Reference<CompileOnSaveAction>> u2a = new WeakHashMap<>();
77
    //@GuardedBy("u2a")
78
    private static final Map<CompileOnSaveAction,URL> a2u = new WeakHashMap<>();
79
80
    private CompileOnSaveActionQuery() {
81
        throw new IllegalStateException("No instance allowed.");    //NOI18N
82
    }
83
    
84
    /**
85
     * Finds Compile On Save performer for given source root.
86
     * @param sourceRoot the source root to find the performer for.
87
     * @return the {@link CompileOnSaveAction} for performing compile on save or
88
     * null in case when the root is not recognized.
89
     */
90
    @CheckForNull
91
    public static CompileOnSaveAction getAction(@NonNull final URL sourceRoot) {
92
        CompileOnSaveAction res;
93
        synchronized (u2a) {
94
            final Reference<CompileOnSaveAction> ref = u2a.get(sourceRoot);
95
            res = ref != null ?
96
                    ref.get() :
97
                    null;
98
        }
99
        if (res == null) {
100
            final Collection<CompileOnSaveAction> actions = findAll(sourceRoot);
101
            res = actions.isEmpty() ?
102
                null :
103
                new ProxyAction(sourceRoot, actions, instances);
104
            synchronized (u2a) {
105
                final Reference<CompileOnSaveAction> ref = u2a.get(sourceRoot);
106
                CompileOnSaveAction tmpRes;
107
                if (ref == null || (tmpRes = ref.get()) == null) {
108
                    u2a.put(sourceRoot, new WeakReference<>(res));
109
                    a2u.put(res, sourceRoot);
110
                } else {
111
                    res = tmpRes;
112
                }
113
            }
114
        }        
115
        return res;
116
    }
117
    
118
    private static Collection<CompileOnSaveAction> findAll(URL root) {
119
        return instances.allInstances().stream()
120
                .map((p) -> p.forRoot(root))
121
                .filter((a) -> a != null)
122
                .collect(Collectors.toList());        
123
    }
124
    
125
    private static final class ProxyAction implements CompileOnSaveAction, LookupListener, ChangeListener {
126
        private static Predicate<CompileOnSaveAction> ALL = (a) -> true;
127
        private static Predicate<CompileOnSaveAction> ACTIVE = (a) -> a.isEnabled();
128
        private final URL root;
129
        private final AtomicReference<Collection<CompileOnSaveAction>> active;
130
        private final ChangeSupport listeners;
131
        
132
        ProxyAction(
133
                @NonNull final URL root,
134
                @NonNull final Collection<CompileOnSaveAction> current,
135
                @NonNull final Lookup.Result<CompileOnSaveAction.Provider> eventSource) {
136
            this.root = root;
137
            this.active = new AtomicReference<>(current);
138
            this.listeners = new ChangeSupport(this);
139
            instances.addLookupListener(WeakListeners.create(
140
                    LookupListener.class,
141
                    this,
142
                    instances));
143
            getActions(ALL)
144
                    .forEach((a) -> a.addChangeListener(WeakListeners.change(this, a)));
145
        }
146
147
        @Override
148
        public Boolean performAction(Context ctx) throws IOException {
149
            return getActions(ACTIVE)
150
                    .findFirst()
151
                    .map((a) -> {
152
                        try {
153
                            return a.performAction(ctx);
154
                        } catch (IOException ioe) {
155
                            return null;
156
                        }
157
                    })
158
                    .orElse(null);
159
        }
160
        
161
        public boolean isEnabled() {
162
            return getActions(ACTIVE)
163
                    .findAny()
164
                    .isPresent();
165
        }
166
167
        @Override
168
        public boolean isUpdateResources() {
169
            return getActions(ACTIVE)
170
                    .findFirst()
171
                    .map((a) -> a.isUpdateResources())
172
                    .orElse(Boolean.FALSE);
173
        }
174
175
        @Override
176
        public boolean isUpdateClasses() {
177
            return getActions(ACTIVE)
178
                    .findFirst()
179
                    .map((a) -> a.isUpdateClasses())
180
                    .orElse(Boolean.FALSE);
181
        }
182
183
        @Override
184
        public void addChangeListener(@NonNull final ChangeListener l) {
185
            this.listeners.addChangeListener(l);
186
        }
187
188
        @Override
189
        public void removeChangeListner(@NonNull final ChangeListener l) {
190
            this.listeners.removeChangeListener(l);
191
        }
192
193
        @Override
194
        public void resultChanged(@NonNull final LookupEvent ev) {
195
            reset();
196
        }
197
        
198
        @Override
199
        public void stateChanged(ChangeEvent e) {
200
            reset();
201
        }
202
        
203
        private void reset() {
204
            this.active.set(null);
205
            listeners.fireChange();
206
        }
207
        
208
        @NonNull
209
        private Stream<CompileOnSaveAction> getActions(@NonNull final Predicate<CompileOnSaveAction> filter) {
210
            Collection<CompileOnSaveAction> res = this.active.get();
211
            if (res == null) {
212
                res = findAll(root);
213
                this.active.compareAndSet(null, res);
214
            }
215
            return res.stream().filter(filter);
216
        }        
217
    }
218
}
(-)a/java.preprocessorbridge/src/org/netbeans/modules/java/preprocessorbridge/spi/CompileOnSaveAction.java (+380 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2016 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.java.preprocessorbridge.spi;
43
44
import java.io.File;
45
import java.io.IOException;
46
import java.net.MalformedURLException;
47
import java.net.URL;
48
import java.util.function.Consumer;
49
import java.util.logging.Level;
50
import java.util.logging.Logger;
51
import javax.swing.event.ChangeListener;
52
import org.netbeans.api.annotations.common.CheckForNull;
53
import org.netbeans.api.annotations.common.NonNull;
54
import org.netbeans.api.annotations.common.NullAllowed;
55
import org.netbeans.api.java.queries.BinaryForSourceQuery;
56
import org.openide.filesystems.FileUtil;
57
import org.openide.util.BaseUtilities;
58
import org.openide.util.Exceptions;
59
import org.openide.util.Lookup;
60
import org.openide.util.Parameters;
61
62
/**
63
 * The Compile On Save performer.
64
 * @since 1.41
65
 * @author Tomas Zezula
66
 */
67
public interface CompileOnSaveAction {
68
69
    /**
70
     * Performs the Compile On Save operation.
71
     * @param ctx the context for Compile On Save operation
72
     * @return true in case of success, false in case of failure, null in case of no changes.
73
     * @throws IOException 
74
     */
75
    Boolean performAction (@NonNull final Context ctx) throws IOException;
76
    /**
77
     * Returns true when this action is enabled.
78
     * The first enabled {@link CompileOnSaveAction} is used for performing the Compile On Save operation.
79
     * @return true when enabled
80
     */
81
    boolean isEnabled();
82
    /**
83
     * Returns true when resources should be synchronized.
84
     * @return true when resources should be copied
85
     */
86
    boolean isUpdateResources();
87
    /**
88
     * Returns true when classes should be synchronized.
89
     * @return true when classes should be copied
90
     */
91
    boolean isUpdateClasses();
92
    /**
93
     * Adds {@link ChangeListener}.
94
     * @param l the listener to be added
95
     */
96
    void addChangeListener(@NonNull ChangeListener l);
97
    /**
98
     * Removes {@link ChangeListener}.
99
     * @param l the listener to be removed
100
     */
101
    void removeChangeListner(@NonNull ChangeListener l);
102
103
    /**
104
     * Compile On Save operation.
105
     */
106
    enum Operation {
107
        /**
108
         * Clean.
109
         */
110
        CLEAN,
111
        /**
112
         * Partial update.
113
         */
114
        UPDATE,
115
        /**
116
         * Full synchronization.
117
         */
118
        SYNC
119
    }
120
121
    /**
122
     * Context of the Compile On Save Operation.
123
     */
124
    final class Context {
125
        private final Operation operation;
126
        private final URL srcRoot;
127
        private final boolean isCopyResources;
128
        private final boolean isKeepResourcesUpToDate;
129
        private final File cacheRoot;
130
        private final Iterable<? extends File> updated;
131
        private final Iterable<? extends File> deleted;
132
        private final Object owner;
133
        private final Consumer<Iterable<File>> firer;
134
        
135
        private Context(
136
                @NonNull final Operation operation,
137
                @NonNull final URL srcRoot,
138
                final boolean isCopyResources,
139
                final boolean isKeepResourcesUpToDate,
140
                @NullAllowed final File cacheRoot,
141
                @NullAllowed final Iterable<? extends File> updated,
142
                @NullAllowed final Iterable<? extends File> deleted,
143
                @NullAllowed final Object owner,
144
                @NullAllowed final Consumer<Iterable<File>> firer) {
145
            this.operation = operation;
146
            this.srcRoot = srcRoot;
147
            this.isCopyResources = isCopyResources;
148
            this.isKeepResourcesUpToDate = isKeepResourcesUpToDate;
149
            this.cacheRoot = cacheRoot;
150
            this.updated = updated;
151
            this.deleted = deleted;
152
            this.owner = owner;
153
            this.firer = firer;
154
        }
155
        
156
        /**
157
         * Returns the kind of the Compile On Save operation.
158
         * @return the {@link Operation}
159
         */
160
        @NonNull
161
        public Operation getOperation() {
162
            return operation;
163
        }
164
        
165
        /**
166
         * Returns the changed files.
167
         * The operation is valid only for {@link Operation#UPDATE}.
168
         * @return the changed files
169
         */
170
        @NonNull
171
        public Iterable<? extends File> getUpdated() {
172
            if (operation != Operation.UPDATE) {
173
                throw new IllegalStateException();
174
            }
175
            return updated;
176
        }
177
        
178
        /**
179
         * Returns the deleted files.
180
         * The operation is valid only for {@link Operation#UPDATE}.
181
         * @return the deleted files
182
         */
183
        @NonNull
184
        public Iterable<? extends File> getDeleted() {
185
            if (operation != Operation.UPDATE) {
186
                throw new IllegalStateException();
187
            }
188
            return deleted;
189
        }
190
        
191
        /**
192
         * Returns true for resources.
193
         * The operation is valid only for {@link Operation#UPDATE} and {@link Operation#SYNC}.
194
         * @return true for resources
195
         */
196
        public boolean isCopyResources() {
197
            if (operation == Operation.CLEAN) {
198
                throw new IllegalStateException();
199
            }
200
            return isCopyResources;
201
        }
202
        
203
        /**
204
         * Returns true if resources should be updated on change.
205
         * The operation is valid only for {@link Operation#SYNC}.
206
         * @return true for update on change
207
         */
208
        public boolean isKeepResourcesUpToDate() {
209
            if (operation != Operation.SYNC) {
210
                throw new IllegalStateException();
211
            }
212
            return isKeepResourcesUpToDate;
213
        }
214
        
215
        /**
216
         * Returns the source root.
217
         * @return the source root
218
         */
219
        @NonNull
220
        public URL getSourceRoot() {
221
            return srcRoot;
222
        }
223
        
224
        /**
225
         * Returns the cache root.
226
         * The operation is valid only for {@link Operation#UPDATE}.
227
         * @return the cache root.
228
         */
229
        @NonNull
230
        public File getCacheRoot() {
231
            if (operation != Operation.UPDATE) {
232
                throw new IllegalStateException();
233
            }
234
            return cacheRoot;
235
        }
236
        
237
        /**
238
         * Returns the target folder.
239
         * @return the target folder.
240
         */
241
        @CheckForNull
242
        public File getTarget() {
243
            return getTarget(srcRoot);
244
        }
245
        
246
        /**
247
         * Returns the root owner.
248
         * The operation is valid only for {@link Operation#SYNC}.
249
         * @return the owner
250
         */
251
        @NonNull
252
        public Object getOwner() {
253
            if (operation != Operation.SYNC) {
254
                throw new IllegalStateException();
255
            }
256
            return owner;
257
        }
258
        
259
        /**
260
         * Fires updated files.
261
         * @param updatedFiles the updated files
262
         */
263
        public void filesUpdated(@NonNull final Iterable<File> updatedFiles) {
264
            if (firer != null) {
265
                firer.accept(updatedFiles);
266
            }
267
        }
268
        
269
        /**
270
         * Creates context for clean operation.
271
         * @param srcRoot the root
272
         * @return the {@link Context} for clean operation
273
         */
274
        @NonNull
275
        public static Context clean(@NonNull final URL srcRoot) {
276
            Parameters.notNull("srcRoot", srcRoot); //NOI18N
277
            return new Context(Operation.CLEAN, srcRoot, false, false, null, null, null, null, null);
278
        }
279
        
280
        /**
281
         * Creates context for update operation.
282
         * @param srcRoot the root
283
         * @param isCopyResources true for resource update
284
         * @param cacheRoot the cache root
285
         * @param updated the changed files
286
         * @param deleted the deleted files
287
         * @param firer the fire callback
288
         * @return the {@link Context} for update operation
289
         */
290
        @NonNull
291
        public static Context update(
292
                @NonNull final URL srcRoot,
293
                final boolean isCopyResources,
294
                @NonNull final File cacheRoot,
295
                @NonNull final Iterable<? extends File> updated,
296
                @NonNull final Iterable<? extends File> deleted,
297
                @NullAllowed final Consumer<Iterable<File>> firer) {
298
            Parameters.notNull("srcRoot", srcRoot); //NOI18N
299
            Parameters.notNull("cacheRoot", cacheRoot); //NOI18N
300
            Parameters.notNull("updated", updated); //NOI18N
301
            Parameters.notNull("deleted", deleted); //NOI18N            
302
            return new Context(
303
                    Operation.UPDATE, srcRoot, isCopyResources, false, cacheRoot, updated, deleted, null, firer);
304
        }
305
        
306
        /**
307
         * Creates context for sync operation.
308
         * @param srcRoot the root
309
         * @param isCopyResources should copy resources
310
         * @param isKeepResourcesUpToDate should synchronize the resources on change
311
         * @param owner the source root owner
312
         * @return the {@link Context} for sync operation
313
         */
314
        @NonNull
315
        public static Context sync(
316
                @NonNull final URL srcRoot,
317
                final boolean isCopyResources,
318
                final boolean isKeepResourcesUpToDate,
319
                @NonNull final Object owner) {
320
            Parameters.notNull("srcRoot", srcRoot); //NOI18N
321
            Parameters.notNull("owner", owner); //NOI18N
322
            return new Context(
323
                    Operation.SYNC, srcRoot, isCopyResources, isKeepResourcesUpToDate, null, null, null, owner, null);
324
        }
325
        
326
        /**
327
         * Returns the target folder for source root.
328
         * @param srcRoot the source root to return target folder for
329
         * @return the target folder
330
         */
331
        @CheckForNull
332
        public static File getTarget(@NonNull URL srcRoot) {
333
            BinaryForSourceQuery.Result binaryRoots = BinaryForSourceQuery.findBinaryRoots(srcRoot);
334
        
335
            File result = null;
336
337
            for (URL u : binaryRoots.getRoots()) {
338
                assert u != null : "Null in BinaryForSourceQuery.Result.roots: " + binaryRoots; //NOI18N
339
                if (u == null) {
340
                    continue;
341
                }
342
                File f = FileUtil.archiveOrDirForURL(u);
343
344
                try {
345
                    if (FileUtil.isArchiveFile(BaseUtilities.toURI(f).toURL())) {
346
                        continue;
347
                    }
348
349
                    if (f != null && result != null) {
350
                        Logger.getLogger(CompileOnSaveAction.class.getName()).log(
351
                                Level.WARNING,
352
                                "More than one binary directory for root: {0}",
353
                                srcRoot.toExternalForm());
354
                        return null;
355
                    }
356
357
                    result = f;
358
                } catch (MalformedURLException ex) {
359
                    Exceptions.printStackTrace(ex);
360
                }
361
            }
362
363
            return result;
364
        }
365
    }
366
367
    /**
368
     * The provider of the {@link CompileOnSaveAction}.
369
     * The instances of the {@link Provider} should be registered in the
370
     * global {@link Lookup}.
371
     */
372
    interface Provider {
373
        /**
374
         * Finds the Compile On Save performer for given source root.
375
         * @param root the root to find the Compile On Save performer for.
376
         * @return the {@link CompileOnSaveAction} or null when the root is not recognized.
377
         */
378
        CompileOnSaveAction forRoot(@NonNull final URL root);
379
    }
380
}
(-)a/java.preprocessorbridge/test/unit/src/org/netbeans/modules/java/preprocessorbridge/api/CompileOnSaveActionQueryTest.java (+256 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2016 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.java.preprocessorbridge.api;
43
44
import java.io.File;
45
import java.io.IOException;
46
import java.net.URL;
47
import javax.swing.event.ChangeListener;
48
import org.junit.After;
49
import org.junit.Before;
50
import org.junit.Test;
51
import org.netbeans.api.annotations.common.CheckForNull;
52
import org.netbeans.api.annotations.common.NonNull;
53
import org.netbeans.junit.NbTestCase;
54
import org.netbeans.modules.java.preprocessorbridge.spi.CompileOnSaveAction;
55
import org.openide.filesystems.FileObject;
56
import org.openide.filesystems.FileUtil;
57
import org.openide.util.BaseUtilities;
58
import org.openide.util.ChangeSupport;
59
import org.openide.util.Parameters;
60
import org.openide.util.test.MockChangeListener;
61
import org.openide.util.test.MockLookup;
62
63
/**
64
 *
65
 * @author Tomas Zezula
66
 */
67
public class CompileOnSaveActionQueryTest extends NbTestCase {
68
    
69
    private URL nonSrcUrl;
70
    private URL srcUrl1, srcUrl2;
71
    private ActionImpl impl1, impl2, impl3;
72
    
73
    public CompileOnSaveActionQueryTest(@NonNull final String name) {
74
        super(name);
75
    }
76
    
77
    @Before
78
    @Override
79
    public void setUp() throws IOException {
80
        clearWorkDir();
81
        final File wd = getWorkDir();
82
        final FileObject src1 = FileUtil.createFolder(FileUtil.normalizeFile(
83
                new File(wd,"src"))); //NOI18N
84
        final FileObject src2 = FileUtil.createFolder(FileUtil.normalizeFile(
85
                new File(wd,"src2"))); //NOI18N
86
        nonSrcUrl = BaseUtilities.toURI(wd).toURL();
87
        srcUrl1 = src1.toURL();
88
        srcUrl2 = src2.toURL();
89
        impl1 = new ActionImpl();
90
        impl2 = new ActionImpl();
91
        impl3 = new ActionImpl();
92
        MockLookup.setInstances(
93
            new ProviderImpl(srcUrl1, impl1),
94
            new ProviderImpl(srcUrl2, impl2),
95
            new ProviderImpl(srcUrl1, impl3));
96
        //Enable all
97
        impl1.setEnabled(true);
98
        impl1.setUpdateClasses(true);
99
        impl1.setUpdateResources(true);
100
        impl2.setEnabled(true);
101
        impl2.setUpdateClasses(true);
102
        impl2.setUpdateResources(true);
103
        impl3.setEnabled(true);
104
        impl3.setUpdateClasses(true);
105
        impl3.setUpdateResources(true);
106
    }
107
    
108
    @After
109
    @Override
110
    public void tearDown() {
111
    }
112
113
    public void testQuery() throws IOException {
114
        CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(nonSrcUrl);
115
        assertNull(a);
116
        assertEquals(0, impl1.getInvocationCountAndReset());
117
        assertEquals(0, impl2.getInvocationCountAndReset());
118
        assertEquals(0, impl3.getInvocationCountAndReset());
119
        a = CompileOnSaveActionQuery.getAction(srcUrl1);
120
        assertNotNull(a);
121
        CompileOnSaveAction.Context ctx = CompileOnSaveAction.Context.clean(srcUrl1);
122
        a.performAction(ctx);
123
        assertEquals(1, impl1.getInvocationCountAndReset());
124
        assertEquals(0, impl2.getInvocationCountAndReset());
125
        assertEquals(0, impl3.getInvocationCountAndReset());
126
        a = CompileOnSaveActionQuery.getAction(srcUrl2);
127
        assertNotNull(a);
128
        ctx = CompileOnSaveAction.Context.clean(srcUrl2);
129
        a.performAction(ctx);
130
        assertEquals(0, impl1.getInvocationCountAndReset());
131
        assertEquals(1, impl2.getInvocationCountAndReset());
132
        assertEquals(0, impl3.getInvocationCountAndReset());        
133
    }
134
    
135
    
136
    public void testQueryChanges() throws IOException {        
137
        CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(srcUrl1);
138
        assertNotNull(a);
139
        CompileOnSaveAction.Context ctx = CompileOnSaveAction.Context.clean(srcUrl1);
140
        a.performAction(ctx);
141
        assertEquals(1, impl1.getInvocationCountAndReset());
142
        assertEquals(0, impl2.getInvocationCountAndReset());
143
        assertEquals(0, impl3.getInvocationCountAndReset());
144
        impl1.setEnabled(false);
145
        a.performAction(ctx);
146
        assertEquals(0, impl1.getInvocationCountAndReset());
147
        assertEquals(0, impl2.getInvocationCountAndReset());
148
        assertEquals(1, impl3.getInvocationCountAndReset());        
149
    }
150
    
151
    
152
    public void testQueryEvents() throws IOException {        
153
        CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(srcUrl1);
154
        assertNotNull(a);
155
        CompileOnSaveAction.Context ctx = CompileOnSaveAction.Context.clean(srcUrl1);
156
        a.performAction(ctx);
157
        assertEquals(1, impl1.getInvocationCountAndReset());
158
        assertEquals(0, impl2.getInvocationCountAndReset());
159
        assertEquals(0, impl3.getInvocationCountAndReset());
160
        final MockChangeListener l = new MockChangeListener();
161
        a.addChangeListener(l);
162
        impl1.setEnabled(false);
163
        l.assertEvent();
164
        a.performAction(ctx);
165
        assertEquals(0, impl1.getInvocationCountAndReset());
166
        assertEquals(0, impl2.getInvocationCountAndReset());
167
        assertEquals(1, impl3.getInvocationCountAndReset());        
168
    }
169
    
170
    
171
    
172
    private static final class ActionImpl implements CompileOnSaveAction {
173
        
174
        private final ChangeSupport listeners = new ChangeSupport(this);
175
        private boolean enabled;
176
        private boolean resEnabled;
177
        private boolean clzEnabled;
178
        private int invocationCount;
179
180
        @Override
181
        public Boolean performAction(Context ctx) throws IOException {
182
            invocationCount++;
183
            return null;
184
        }
185
        
186
        public boolean isEnabled() {
187
            return enabled;
188
        }
189
190
        @Override
191
        public boolean isUpdateResources() {
192
            return resEnabled;
193
        }
194
195
        @Override
196
        public boolean isUpdateClasses() {
197
            return clzEnabled;
198
        }
199
200
        @Override
201
        public void addChangeListener(ChangeListener l) {
202
            listeners.addChangeListener(l);
203
        }
204
205
        @Override
206
        public void removeChangeListner(ChangeListener l) {            
207
            listeners.removeChangeListener(l);
208
        }
209
        
210
        void setUpdateResources(final boolean v) {
211
            this.resEnabled = v;
212
            listeners.fireChange();
213
        }
214
        
215
        void setUpdateClasses(final boolean v) {
216
            this.clzEnabled = v;
217
            listeners.fireChange();
218
        }
219
        
220
        void setEnabled(final boolean v) {
221
            this.enabled = v;
222
            listeners.fireChange();
223
        }
224
        
225
        int getInvocationCountAndReset() {
226
            int res = invocationCount;
227
            invocationCount = 0;
228
            return res;
229
        }
230
    }
231
    
232
    private static final class ProviderImpl implements CompileOnSaveAction.Provider {
233
        private final URL root;
234
        private final CompileOnSaveAction action;
235
        
236
        ProviderImpl(
237
                @NonNull final URL root,
238
                @NonNull final CompileOnSaveAction action) {
239
            Parameters.notNull("root", root);   //NOI18N
240
            Parameters.notNull("action", action);   //NOI18N
241
            this.root = root;
242
            this.action = action;
243
        }       
244
        
245
        @Override
246
        @CheckForNull
247
        public CompileOnSaveAction forRoot(@NonNull final URL root) {
248
            if (this.root.equals(root)) {
249
                return this.action;
250
            }
251
            return null;
252
        }        
253
    }
254
        
255
    
256
}
(-)a/java.source.ant/antsrc/org/netbeans/modules/java/source/ant/CosUpdated.java (+239 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2016 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.java.source.ant;
43
44
import java.io.File;
45
import java.io.IOException;
46
import java.io.InputStream;
47
import java.io.OutputStream;
48
import java.util.Iterator;
49
import java.util.Objects;
50
import org.apache.tools.ant.BuildException;
51
import org.apache.tools.ant.Project;
52
import org.apache.tools.ant.Task;
53
import org.apache.tools.ant.types.FileSet;
54
import org.apache.tools.ant.types.Reference;
55
import org.apache.tools.ant.types.Resource;
56
57
/**
58
 *
59
 * @author Tomas Zezula
60
 */
61
public final class CosUpdated extends Task {
62
    private String id;
63
    private File srcdir;
64
    private String includes;
65
    
66
    public void setId(final String id) {
67
        id.getClass();
68
        this.id = id;
69
    }
70
    
71
    
72
    public String getId() {
73
        return this.id;
74
    }
75
    
76
    public void setSrcdir(final File dir) {
77
        this.srcdir = dir;
78
    }
79
    
80
    public File getSrcdir() {
81
        return this.srcdir;
82
    }
83
    
84
    public void setIncludes(final String includes) {
85
        this.includes = includes;
86
    }
87
    
88
    public String getIncludes() {
89
        return this.includes;
90
    }
91
92
    @Override
93
    public void execute() throws BuildException {
94
        if (this.id == null || this.id.isEmpty()) {
95
            throw new BuildException("The id has to be set.");    //NOI18N
96
        }
97
        if (this.srcdir == null || !this.srcdir.isDirectory()) {
98
            throw new BuildException("The srcdir has to point to a directory.");    //NOI18N
99
        }
100
        if (this.includes == null || this.includes.isEmpty()) {
101
            throw new BuildException("The includes has to be set.");    //NOI18N
102
        }
103
        final Project prj = getProject();
104
        final CosFileSet cfs = new CosFileSet();
105
        cfs.setProject(prj);
106
        cfs.setDir(this.srcdir);
107
        for (String include : includes.split(",")) {    //NOI18N
108
            include = include.trim();
109
            if (!include.isEmpty()) {
110
                cfs.createInclude().setName(include);
111
            }
112
        }
113
        prj.addReference(this.id, cfs);
114
    }
115
    
116
    private static final class CosFileSet extends FileSet {
117
118
        @Override
119
        public Iterator<Resource> iterator() {
120
            return new CosFileSetIterator(super.iterator());
121
        }
122
123
        @Override
124
        public boolean isFilesystemOnly() {
125
            return false;
126
        }
127
        
128
        private static final class CosFileSetIterator implements Iterator<Resource> {
129
            
130
            private final Iterator<Resource> delegate;
131
            
132
            CosFileSetIterator(final Iterator<Resource> delegate) {
133
                delegate.getClass();
134
                this.delegate = delegate;
135
            }
136
137
            @Override
138
            public boolean hasNext() {
139
                return delegate.hasNext();
140
            }
141
142
            @Override
143
            public Resource next() {
144
                return new CosResource(delegate.next());
145
            }            
146
        }
147
        
148
        private static final class CosResource extends Resource {
149
            
150
            private final Resource delegate;
151
            
152
            CosResource(final Resource delegate) {
153
                delegate.getClass();
154
                this.delegate = delegate;
155
            }
156
157
            @Override
158
            public void setRefid(Reference r) {
159
                throw tooManyAttributes();
160
            }
161
162
            @Override
163
            public String getName() {
164
                final String name = delegate.getName();
165
                return name.replace(".sig", ".class");  //NOI18N
166
            }
167
168
            @Override
169
            public boolean isExists() {
170
                return delegate.isExists();
171
            }
172
173
            @Override
174
            public long getLastModified() {
175
                return delegate.getLastModified();
176
            }
177
178
            @Override
179
            public boolean isDirectory() {
180
                return delegate.isDirectory();
181
            }
182
183
            @Override
184
            public long getSize() {
185
                return delegate.getSize();
186
            }
187
188
            @Override
189
            public InputStream getInputStream() throws IOException {
190
                return delegate.getInputStream();
191
            }
192
193
            @Override
194
            public OutputStream getOutputStream() throws IOException {
195
                return delegate.getOutputStream();
196
            }
197
198
            @Override
199
            public int compareTo(Resource another) {
200
                if (another instanceof CosResource) {
201
                    return delegate.compareTo(((CosResource)another).delegate);
202
                } else {
203
                    return toString().compareTo(String.valueOf(another));
204
                }
205
            }
206
207
            @Override
208
            public int hashCode() {
209
                int hash = 3;
210
                hash = 71 * hash + Objects.hashCode(this.delegate);
211
                return hash;
212
            }
213
214
            @Override
215
            public boolean equals(Object obj) {
216
                if (this == obj) {
217
                    return true;
218
                }
219
                if (obj == null) {
220
                    return false;
221
                }
222
                if (getClass() != obj.getClass()) {
223
                    return false;
224
                }
225
                return Objects.equals(this.delegate, ((CosResource)obj).delegate);
226
            }
227
228
            @Override
229
            public String toString() {
230
                return delegate.toString();
231
            }
232
233
            @Override
234
            public boolean isFilesystemOnly() {
235
                return false;
236
            }
237
        }
238
    }
239
}
(-)a/java.source.ant/antsrc/org/netbeans/modules/java/source/ant/antlib.xml (+1 lines)
Lines 47-50 Link Here
47
    <taskdef name="javac" classname="org.netbeans.modules.java.source.ant.JavacTask"/>
47
    <taskdef name="javac" classname="org.netbeans.modules.java.source.ant.JavacTask"/>
48
    <taskdef name="delete" classname="org.netbeans.modules.java.source.ant.DeleteTask"/>
48
    <taskdef name="delete" classname="org.netbeans.modules.java.source.ant.DeleteTask"/>
49
    <taskdef name="translate-classpath" classname="org.netbeans.modules.java.source.ant.TranslateClassPath"/>
49
    <taskdef name="translate-classpath" classname="org.netbeans.modules.java.source.ant.TranslateClassPath"/>
50
    <taskdef name="cos-updated" classname="org.netbeans.modules.java.source.ant.CosUpdated"/>
50
</antlib>
51
</antlib>
(-)a/java.source.base/nbproject/project.xml (-1 / +1 lines)
Lines 203-209 Link Here
203
                    <build-prerequisite/>
203
                    <build-prerequisite/>
204
                    <compile-dependency/>
204
                    <compile-dependency/>
205
                    <run-dependency>
205
                    <run-dependency>
206
                        <specification-version>1.22</specification-version>
206
                        <specification-version>1.41</specification-version>
207
                    </run-dependency>
207
                    </run-dependency>
208
                </dependency>
208
                </dependency>
209
                <dependency>
209
                <dependency>
(-)a/java.source.base/src/org/netbeans/modules/java/source/indexing/COSSynchronizingIndexer.java (-2 / +7 lines)
Lines 54-59 Link Here
54
import java.util.logging.Level;
54
import java.util.logging.Level;
55
import java.util.logging.Logger;
55
import java.util.logging.Logger;
56
import org.netbeans.api.java.classpath.ClassPath;
56
import org.netbeans.api.java.classpath.ClassPath;
57
import org.netbeans.modules.java.preprocessorbridge.spi.CompileOnSaveAction;
57
import org.netbeans.modules.java.source.usages.BuildArtifactMapperImpl;
58
import org.netbeans.modules.java.source.usages.BuildArtifactMapperImpl;
58
import org.netbeans.modules.parsing.impl.indexing.IndexerCache;
59
import org.netbeans.modules.parsing.impl.indexing.IndexerCache;
59
import org.netbeans.modules.parsing.impl.indexing.IndexerCache.IndexerInfo;
60
import org.netbeans.modules.parsing.impl.indexing.IndexerCache.IndexerInfo;
Lines 80-86 Link Here
80
        if (FileUtil.getArchiveFile(rootURL) != null) {
81
        if (FileUtil.getArchiveFile(rootURL) != null) {
81
            return;
82
            return;
82
        }
83
        }
83
        if (!BuildArtifactMapperImpl.isUpdateResources(BuildArtifactMapperImpl.getTargetFolder(rootURL))) {
84
        if (!BuildArtifactMapperImpl.isUpdateResources(rootURL)) {
84
            return ;
85
            return ;
85
        }
86
        }
86
87
Lines 156-162 Link Here
156
157
157
        @Override
158
        @Override
158
        public void filesDeleted(Iterable<? extends Indexable> deleted, Context context) {
159
        public void filesDeleted(Iterable<? extends Indexable> deleted, Context context) {
159
            if (BuildArtifactMapperImpl.getTargetFolder(context.getRootURI()) == null) {
160
            final File target = CompileOnSaveAction.Context.getTarget(context.getRootURI());
161
            if (target == null) {
162
                return;
163
            }            
164
            if (!BuildArtifactMapperImpl.isUpdateClasses(context.getRootURI())) {
160
                return ;
165
                return ;
161
            }
166
            }
162
167
(-)a/java.source.base/src/org/netbeans/modules/java/source/usages/BuildArtifactMapperImpl.java (-226 / +340 lines)
Lines 68-84 Link Here
68
68
69
import javax.swing.event.ChangeEvent;
69
import javax.swing.event.ChangeEvent;
70
import javax.swing.event.ChangeListener;
70
import javax.swing.event.ChangeListener;
71
import org.netbeans.api.annotations.common.NonNull;
72
import org.netbeans.api.annotations.common.NullAllowed;
71
73
72
import org.netbeans.api.java.classpath.ClassPath;
74
import org.netbeans.api.java.classpath.ClassPath;
73
import org.netbeans.api.java.queries.AnnotationProcessingQuery;
75
import org.netbeans.api.java.queries.AnnotationProcessingQuery;
74
import org.netbeans.api.java.queries.BinaryForSourceQuery;
75
import org.netbeans.api.java.queries.BinaryForSourceQuery.Result;
76
import org.netbeans.api.java.queries.SourceForBinaryQuery;
76
import org.netbeans.api.java.queries.SourceForBinaryQuery;
77
import org.netbeans.api.java.source.BuildArtifactMapper.ArtifactsUpdated;
77
import org.netbeans.api.java.source.BuildArtifactMapper.ArtifactsUpdated;
78
import org.netbeans.api.java.source.SourceUtils;
78
import org.netbeans.api.java.source.SourceUtils;
79
import org.netbeans.api.queries.FileBuiltQuery;
79
import org.netbeans.api.queries.FileBuiltQuery;
80
import org.netbeans.api.queries.FileBuiltQuery.Status;
80
import org.netbeans.api.queries.FileBuiltQuery.Status;
81
import org.netbeans.api.queries.VisibilityQuery;
81
import org.netbeans.api.queries.VisibilityQuery;
82
import org.netbeans.modules.java.preprocessorbridge.api.CompileOnSaveActionQuery;
83
import org.netbeans.modules.java.preprocessorbridge.spi.CompileOnSaveAction;
82
import org.netbeans.modules.java.source.indexing.COSSynchronizingIndexer;
84
import org.netbeans.modules.java.source.indexing.COSSynchronizingIndexer;
83
import org.netbeans.modules.java.source.indexing.JavaIndex;
85
import org.netbeans.modules.java.source.indexing.JavaIndex;
84
import org.netbeans.modules.java.source.parsing.FileObjects;
86
import org.netbeans.modules.java.source.parsing.FileObjects;
Lines 90-96 Link Here
90
import org.netbeans.modules.parsing.spi.indexing.ErrorsCache;
92
import org.netbeans.modules.parsing.spi.indexing.ErrorsCache;
91
import org.netbeans.spi.queries.FileBuiltQueryImplementation;
93
import org.netbeans.spi.queries.FileBuiltQueryImplementation;
92
import org.openide.filesystems.FileObject;
94
import org.openide.filesystems.FileObject;
93
import org.openide.filesystems.FileStateInvalidException;
94
import org.openide.filesystems.FileUtil;
95
import org.openide.filesystems.FileUtil;
95
import org.openide.util.ChangeSupport;
96
import org.openide.util.ChangeSupport;
96
import org.openide.util.Exceptions;
97
import org.openide.util.Exceptions;
Lines 98-104 Link Here
98
import org.openide.util.RequestProcessor;
99
import org.openide.util.RequestProcessor;
99
import org.openide.util.BaseUtilities;
100
import org.openide.util.BaseUtilities;
100
import org.openide.util.Lookup;
101
import org.openide.util.Lookup;
102
import org.openide.util.WeakListeners;
101
import org.openide.util.WeakSet;
103
import org.openide.util.WeakSet;
104
import org.openide.util.lookup.ServiceProvider;
102
105
103
/**
106
/**
104
 *
107
 *
Lines 175-403 Link Here
175
        }
178
        }
176
    }
179
    }
177
    
180
    
178
    private static File getTarget(URL source) {
179
        Result binaryRoots = BinaryForSourceQuery.findBinaryRoots(source);
180
        
181
        File result = null;
182
        
183
        for (URL u : binaryRoots.getRoots()) {
184
            assert u != null : "Null in BinaryForSourceQuery.Result.roots: " + binaryRoots; //NOI18N
185
            if (u == null) {
186
                continue;
187
            }
188
            File f = FileUtil.archiveOrDirForURL(u);
189
190
            try {
191
                if (FileUtil.isArchiveFile(BaseUtilities.toURI(f).toURL())) {
192
                    continue;
193
                }
194
            
195
                if (f != null && result != null) {
196
                    Logger.getLogger(BuildArtifactMapperImpl.class.getName()).log(Level.WARNING, "More than one binary directory for root: {0}", source.toExternalForm());
197
                    return null;
198
                }
199
200
                result = f;
201
            } catch (MalformedURLException ex) {
202
                Exceptions.printStackTrace(ex);
203
            }
204
        }
205
        
206
        return result;
207
    }
208
209
    @SuppressWarnings("deprecation")
181
    @SuppressWarnings("deprecation")
210
    public static Boolean ensureBuilt(URL sourceRoot, Object context, boolean copyResources, boolean keepResourceUpToDate) throws IOException {
182
    public static Boolean ensureBuilt(URL sourceRoot, Object context, boolean copyResources, boolean keepResourceUpToDate) throws IOException {
211
        File targetFolder = getTarget(sourceRoot);
183
        final CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(sourceRoot);
212
        
184
        if (a != null) {
213
        if (targetFolder == null) {
185
            final CompileOnSaveAction.Context ctx = CompileOnSaveAction.Context.sync(
214
            return null;
186
                    sourceRoot,
187
                    copyResources,
188
                    keepResourceUpToDate,
189
                    context);
190
            return a.performAction(ctx);
215
        }
191
        }
216
        
192
        return null;
217
        try {
218
            SourceUtils.waitScanFinished();
219
        } catch (InterruptedException e) {
220
            //Not Important
221
            LOG.log(Level.FINE, null, e);
222
            return null;
223
        }
224
225
        if (JavaIndex.ensureAttributeValue(sourceRoot, DIRTY_ROOT, null)) {
226
            IndexingManager.getDefault().refreshIndexAndWait(sourceRoot, null);
227
        }
228
229
        if (JavaIndex.getAttribute(sourceRoot, DIRTY_ROOT, null) != null) {
230
            return false;
231
        }
232
        
233
        FileObject[][] sources = new FileObject[1][];
234
        
235
        if (!protectAgainstErrors(targetFolder, sources, context)) {
236
            return false;
237
        }
238
        
239
        File tagFile = new File(targetFolder, TAG_FILE_NAME);
240
        File tagUpdateResourcesFile = new File(targetFolder, TAG_UPDATE_RESOURCES);
241
        final boolean forceResourceCopy = copyResources && keepResourceUpToDate && !tagUpdateResourcesFile.exists();
242
        final boolean cosActive = tagFile.exists();
243
        if (cosActive && !forceResourceCopy) {
244
            return true;
245
        }
246
247
        if (!cosActive) {
248
            delete(targetFolder, false/*#161085: cleanCompletely*/);
249
        }
250
251
        if (!targetFolder.exists() && !targetFolder.mkdirs()) {
252
            throw new IOException("Cannot create destination folder: " + targetFolder.getAbsolutePath());
253
        }
254
255
        sources(targetFolder, sources);
256
257
        for (int i = sources[0].length - 1; i>=0; i--) {
258
            final FileObject sr = sources[0][i];
259
            if (!cosActive) {
260
                URL srURL = sr.toURL();
261
                File index = JavaIndex.getClassFolder(srURL, true);
262
263
                if (index == null) {
264
                    //#181992: (not nice) ignore the annotation processing target directory:
265
                    if (srURL.equals(AnnotationProcessingQuery.getAnnotationProcessingOptions(sr).sourceOutputDirectory())) {
266
                        continue;
267
                    }
268
269
                    return null;
270
                }
271
272
                copyRecursively(index, targetFolder);
273
            }
274
275
            if (copyResources) {
276
                Set<String> javaMimeTypes = COSSynchronizingIndexer.gatherJavaMimeTypes();
277
                String[]    javaMimeTypesArr = javaMimeTypes.toArray(new String[0]);
278
279
                copyRecursively(sr, targetFolder, javaMimeTypes, javaMimeTypesArr);
280
            }
281
        }
282
283
        if (!cosActive) {
284
            new FileOutputStream(tagFile).close();
285
        }
286
287
        if (keepResourceUpToDate)
288
            new FileOutputStream(tagUpdateResourcesFile).close();
289
        
290
        return true;
291
    }
193
    }
292
    
194
    
293
    @SuppressWarnings("deprecation")
195
    @SuppressWarnings("deprecation")
294
    public static Boolean clean(URL sourceRoot) throws IOException {
196
    public static Boolean clean(URL sourceRoot) throws IOException {
295
        File targetFolder = getTarget(sourceRoot);
197
        final CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(sourceRoot);
296
198
        if (a != null) {
297
        if (targetFolder == null) {
199
            final CompileOnSaveAction.Context ctx = CompileOnSaveAction.Context.clean(sourceRoot);
298
            return null;
200
            return a.performAction(ctx);
299
        }
201
        }
300
301
        File tagFile = new File(targetFolder, TAG_FILE_NAME);
302
303
        if (!tagFile.exists()) {
304
            return null;
305
        }
306
307
        try {
308
            SourceUtils.waitScanFinished();
309
        } catch (InterruptedException e) {
310
            //Not Important
311
            LOG.log(Level.FINE, null, e);
312
            return false;
313
        }
314
315
        delete(targetFolder, false);
316
        delete(tagFile, true);
317
318
        return null;
202
        return null;
319
    }
203
    }
320
204
321
    public static File getTargetFolder(URL sourceRoot) {
205
    public static boolean isUpdateClasses(URL sourceRoot) {
322
        File targetFolder = getTarget(sourceRoot);
206
        final CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(sourceRoot);
323
207
        return a != null ? 
324
        if (targetFolder == null) {
208
                a.isUpdateClasses():
325
            return null;
209
                false;        
326
        }
327
328
        if (!new File(targetFolder, TAG_FILE_NAME).exists()) {
329
            return null;
330
        }
331
        
332
        return targetFolder;
333
    }
210
    }
334
211
335
    public static boolean isUpdateResources(File targetFolder) {
212
    public static boolean isUpdateResources(URL srcRoot) {
336
        return targetFolder != null && new File(targetFolder, TAG_UPDATE_RESOURCES).exists();
213
        final CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(srcRoot);
214
        return a != null ? 
215
                a.isUpdateResources():
216
                false;                
337
    }
217
    }
338
218
339
    public static void classCacheUpdated(URL sourceRoot, File cacheRoot, Iterable<File> deleted, Iterable<File> updated, boolean resource) {
219
    public static void classCacheUpdated(URL sourceRoot, File cacheRoot, Iterable<File> deleted, Iterable<File> updated, boolean resource) {
340
        if (!deleted.iterator().hasNext() && !updated.iterator().hasNext()) {
220
        final CompileOnSaveAction a = CompileOnSaveActionQuery.getAction(sourceRoot);
341
            return ;
221
        if (a != null) {
342
        }
343
344
        File targetFolder = getTargetFolder(sourceRoot);
345
346
        if (targetFolder == null) {
347
            return ;
348
        }
349
350
        if (resource && !isUpdateResources(targetFolder)) {
351
            return ;
352
        }
353
        
354
        List<File> updatedFiles = new LinkedList<File>();
355
356
        for (File deletedFile : deleted) {
357
            final String relPath = relativizeFile(cacheRoot, deletedFile);
358
            if (relPath == null) {
359
                throw new IllegalArgumentException (String.format(
360
                    "Deleted file: %s is not under cache root: %s, (normalized file: %s).", //NOI18N
361
                    deletedFile.getAbsolutePath(),
362
                    cacheRoot.getAbsolutePath(),
363
                    FileUtil.normalizeFile(deletedFile).getAbsolutePath()));
364
            }
365
            File toDelete = resolveFile(targetFolder, relPath);
366
            
367
            toDelete.delete();
368
            updatedFiles.add(toDelete);
369
        }
370
371
        for (File updatedFile : updated) {
372
            final String relPath = relativizeFile(cacheRoot, updatedFile);
373
            if (relPath == null) {
374
                throw new IllegalArgumentException (String.format(
375
                    "Updated file: %s is not under cache root: %s, (normalized file: %s).", //NOI18N
376
                    updatedFile.getAbsolutePath(),
377
                    cacheRoot.getAbsolutePath(),
378
                    FileUtil.normalizeFile(updatedFile).getAbsolutePath()));
379
            }
380
            File target = resolveFile(targetFolder, relPath);                        
381
382
            try {
222
            try {
383
                copyFile(updatedFile, target);
223
                final CompileOnSaveAction.Context ctx = CompileOnSaveAction.Context.update(
384
                updatedFiles.add(target);
224
                        sourceRoot,
225
                        resource,
226
                        cacheRoot,
227
                        updated,
228
                        deleted,
229
                        (updatedFiles) -> fire(sourceRoot, updatedFiles));
230
                a.performAction(ctx);
385
            } catch (IOException ex) {
231
            } catch (IOException ex) {
386
                Exceptions.printStackTrace(ex);
232
                Exceptions.printStackTrace(ex);
387
            }
233
            }
388
        }
234
        }
389
235
    }
390
        if (updatedFiles.size() > 0) {
236
    
237
    private static void fire(
238
            @NonNull final URL sourceRoot,
239
            @NonNull final Iterable<File> updatedFiles) {
240
        if (updatedFiles.iterator().hasNext()) {
391
            Set<ArtifactsUpdated> listeners;
241
            Set<ArtifactsUpdated> listeners;
392
393
            synchronized (BuildArtifactMapperImpl.class) {
242
            synchronized (BuildArtifactMapperImpl.class) {
394
                listeners = source2Listener.get(sourceRoot);
243
                listeners = source2Listener.get(sourceRoot);
395
396
                if (listeners != null) {
244
                if (listeners != null) {
397
                    listeners = new HashSet<ArtifactsUpdated>(listeners);
245
                    listeners = new HashSet<>(listeners);
398
                }
246
                }
399
            }
247
            }
400
401
            if (listeners != null) {
248
            if (listeners != null) {
402
                for (ArtifactsUpdated listener : listeners) {
249
                for (ArtifactsUpdated listener : listeners) {
403
                    listener.artifactsUpdated(updatedFiles);
250
                    listener.artifactsUpdated(updatedFiles);
Lines 405-411 Link Here
405
            }
252
            }
406
        }
253
        }
407
    }
254
    }
408
255
    
409
    private static void copyFile(File updatedFile, File target) throws IOException {
256
    private static void copyFile(File updatedFile, File target) throws IOException {
410
        final File parent = target.getParentFile();
257
        final File parent = target.getParentFile();
411
        if (parent != null && !parent.exists()) {
258
        if (parent != null && !parent.exists()) {
Lines 692-750 Link Here
692
                    return delegate;
539
                    return delegate;
693
                }
540
                }
694
541
695
                File target = getTarget(owner.getURL());
542
                final CompileOnSaveAction action = CompileOnSaveActionQuery.getAction(owner.toURL());
696
                File tagFile = FileUtil.normalizeFile(new File(target, TAG_FILE_NAME));
543
                if (action == null) {
544
                    return delegate;
545
                }
697
546
698
                synchronized(this) {
547
                synchronized(this) {
699
                    Reference<FileChangeListenerImpl> ref = file2Listener.get(tagFile);
700
                    FileChangeListenerImpl l = ref != null ? ref.get() : null;
701
702
                    if (l == null) {
703
                        file2Listener.put(tagFile, new WeakReference<FileChangeListenerImpl>(l = new FileChangeListenerImpl()));
704
                        listener2File.put(l, tagFile);
705
                        FileChangeSupport.DEFAULT.addListener(l, tagFile);
706
                    }
707
708
                    Reference<Status> prevRef = file2Status.get(file);
548
                    Reference<Status> prevRef = file2Status.get(file);
709
                    result = prevRef != null ? prevRef.get() : null;
549
                    result = prevRef != null ? prevRef.get() : null;
710
550
711
                    if (result == null) {
551
                    if (result == null) {
712
                        file2Status.put(file, new WeakReference<Status>(result = new FileBuiltQueryStatusImpl(delegate, tagFile, l)));
552
                        file2Status.put(file, new WeakReference<Status>(result = new FileBuiltQueryStatusImpl(delegate, action)));
713
                    }
553
                    }
714
554
715
                    return result;
555
                    return result;
716
                }
556
                }            
717
            } catch (FileStateInvalidException ex) {
718
                Exceptions.printStackTrace(ex);
719
                return null;
720
            } finally {
557
            } finally {
721
                recursive.remove();
558
                recursive.remove();
722
            }
559
            }
723
        }
560
        }
724
        
561
        
725
    }
562
    }
726
727
    private static Map<File, Reference<FileChangeListenerImpl>> file2Listener = new WeakHashMap<File, Reference<FileChangeListenerImpl>>();
728
    private static Map<FileChangeListenerImpl, File> listener2File = new WeakHashMap<FileChangeListenerImpl, File>();
729
    
563
    
730
    private static final class FileBuiltQueryStatusImpl implements FileBuiltQuery.Status, ChangeListener {
564
    private static final class FileBuiltQueryStatusImpl implements FileBuiltQuery.Status, ChangeListener {
731
565
732
        private final FileBuiltQuery.Status delegate;
566
        private final FileBuiltQuery.Status delegate;
733
        private final File tag;
567
        private final CompileOnSaveAction action;
734
        private final FileChangeListenerImpl fileListener;
735
        private final ChangeSupport cs = new ChangeSupport(this);
568
        private final ChangeSupport cs = new ChangeSupport(this);
736
569
737
        public FileBuiltQueryStatusImpl(Status delegate, File tag, FileChangeListenerImpl fileListener) {
570
        public FileBuiltQueryStatusImpl(Status delegate, CompileOnSaveAction action) {
738
            this.delegate = delegate;
571
            this.delegate = delegate;
739
            this.tag = tag;
572
            this.action = action;
740
            this.fileListener = fileListener;
741
573
742
            delegate.addChangeListener(this);
574
            delegate.addChangeListener(this);
743
            fileListener.addListener(this);
575
            action.addChangeListener(WeakListeners.change(this, action));
744
        }
576
        }
745
577
746
        public boolean isBuilt() {
578
        public boolean isBuilt() {
747
            return delegate.isBuilt() || tag.canRead();
579
            return delegate.isBuilt() || action.isUpdateClasses();
748
        }
580
        }
749
581
750
        public void addChangeListener(ChangeListener l) {
582
        public void addChangeListener(ChangeListener l) {
Lines 794-798 Link Here
794
                }
626
                }
795
            });
627
            });
796
        }
628
        }
629
    }       
630
631
    private static final class DefaultCompileOnSaveAction implements CompileOnSaveAction, ChangeListener {
632
        //@GuardedBy("file2Listener")
633
        private static Map<File, Reference<FileChangeListenerImpl>> file2Listener = new WeakHashMap<>();
634
        //@GuardedBy("file2Listener")
635
        private static Map<FileChangeListenerImpl, File> listener2File = new WeakHashMap<>();
636
        
637
        private final URL root;
638
        private final ChangeSupport cs;
639
        //@GuardedBy("file2Listener")
640
        private FileChangeListenerImpl listenerDelegate;
641
        
642
        DefaultCompileOnSaveAction(@NonNull final URL root) {
643
            this.root = root;
644
            this.cs = new ChangeSupport(this);
645
        }
646
        
647
        public boolean isEnabled() {
648
            return true;
649
        }
650
        
651
        @Override
652
        public boolean isUpdateClasses() {
653
            return isUpdateClasses(CompileOnSaveAction.Context.getTarget(root));
654
        }
655
        
656
        @Override
657
        public boolean isUpdateResources() {
658
            return isUpdateResources(CompileOnSaveAction.Context.getTarget(root));
659
        }
660
661
        @Override
662
        public Boolean performAction(@NonNull final Context ctx) throws IOException {
663
            assert root.equals(ctx.getSourceRoot());
664
            switch (ctx.getOperation()) {
665
                case CLEAN:
666
                    return performClean(ctx);
667
                case SYNC:
668
                    return performSync(ctx);
669
                case UPDATE:
670
                    return performUpdate(ctx);
671
                default:
672
            }       throw new IllegalArgumentException(String.valueOf(ctx.getOperation()));
673
        }
674
675
        @Override
676
        public void addChangeListener(@NonNull final ChangeListener listener) {
677
            final File target = CompileOnSaveAction.Context.getTarget(root);
678
            final File tagFile = FileUtil.normalizeFile(new File(target, TAG_FILE_NAME));
679
            synchronized (file2Listener) {
680
                if (listenerDelegate == null) {
681
                    final Reference<FileChangeListenerImpl> ref = file2Listener.get(tagFile);
682
                    FileChangeListenerImpl l = ref != null ? ref.get() : null;
683
                    if (l == null) {
684
                        file2Listener.put(tagFile, new WeakReference<FileChangeListenerImpl>(l = new FileChangeListenerImpl()));
685
                        listener2File.put(l, tagFile);
686
                        FileChangeSupport.DEFAULT.addListener(l, tagFile);
687
                        //Need to hold l
688
                    }
689
                    listenerDelegate = l;
690
                    listenerDelegate.addListener(this);
691
                }
692
            }
693
            cs.addChangeListener(listener);
694
        }
695
696
        @Override
697
        public void removeChangeListner(@NonNull final ChangeListener listener) {
698
            cs.removeChangeListener(listener);
699
        }
700
701
        @Override
702
        public void stateChanged(@NonNull final ChangeEvent e) {
703
            cs.fireChange();
704
        }                
705
        
706
        private Boolean performClean(@NonNull final Context ctx) throws IOException {
707
            final File targetFolder = ctx.getTarget();
708
709
            if (targetFolder == null) {
710
                return null;
711
            }
712
713
            File tagFile = new File(targetFolder, TAG_FILE_NAME);
714
715
            if (!tagFile.exists()) {
716
                return null;
717
            }
718
719
            try {
720
                SourceUtils.waitScanFinished();
721
            } catch (InterruptedException e) {
722
                //Not Important
723
                LOG.log(Level.FINE, null, e);
724
                return false;
725
            }
726
727
            delete(targetFolder, false);
728
            delete(tagFile, true);
729
730
            return null;
731
        }
732
        
733
        private Boolean performSync(@NonNull final Context ctx) throws IOException {
734
            final URL sourceRoot = ctx.getSourceRoot();
735
            final File targetFolder = ctx.getTarget();
736
            final boolean copyResources = ctx.isCopyResources();
737
            final boolean keepResourceUpToDate = ctx.isKeepResourcesUpToDate();
738
            final Object context = ctx.getOwner();
739
        
740
            if (targetFolder == null) {
741
                return null;
742
            }
743
        
744
            try {
745
                SourceUtils.waitScanFinished();
746
            } catch (InterruptedException e) {
747
                //Not Important
748
                LOG.log(Level.FINE, null, e);
749
                return null;
750
            }
751
752
            if (JavaIndex.ensureAttributeValue(sourceRoot, DIRTY_ROOT, null)) {
753
                IndexingManager.getDefault().refreshIndexAndWait(sourceRoot, null);
754
            }
755
756
            if (JavaIndex.getAttribute(sourceRoot, DIRTY_ROOT, null) != null) {
757
                return false;
758
            }
759
        
760
            FileObject[][] sources = new FileObject[1][];
761
        
762
            if (!protectAgainstErrors(targetFolder, sources, context)) {
763
                return false;
764
            }
765
        
766
            File tagFile = new File(targetFolder, TAG_FILE_NAME);
767
            File tagUpdateResourcesFile = new File(targetFolder, TAG_UPDATE_RESOURCES);
768
            final boolean forceResourceCopy = copyResources && keepResourceUpToDate && !tagUpdateResourcesFile.exists();
769
            final boolean cosActive = tagFile.exists();
770
            if (cosActive && !forceResourceCopy) {
771
                return true;
772
            }
773
774
            if (!cosActive) {
775
                delete(targetFolder, false/*#161085: cleanCompletely*/);
776
            }
777
778
            if (!targetFolder.exists() && !targetFolder.mkdirs()) {
779
                throw new IOException("Cannot create destination folder: " + targetFolder.getAbsolutePath());
780
            }
781
782
            sources(targetFolder, sources);
783
784
            for (int i = sources[0].length - 1; i>=0; i--) {
785
                final FileObject sr = sources[0][i];
786
                if (!cosActive) {
787
                    URL srURL = sr.toURL();
788
                    File index = JavaIndex.getClassFolder(srURL, true);
789
790
                    if (index == null) {
791
                        //#181992: (not nice) ignore the annotation processing target directory:
792
                        if (srURL.equals(AnnotationProcessingQuery.getAnnotationProcessingOptions(sr).sourceOutputDirectory())) {
793
                            continue;
794
                        }
795
796
                        return null;
797
                    }
798
799
                    copyRecursively(index, targetFolder);
800
                }
801
802
                if (copyResources) {
803
                    Set<String> javaMimeTypes = COSSynchronizingIndexer.gatherJavaMimeTypes();
804
                    String[]    javaMimeTypesArr = javaMimeTypes.toArray(new String[0]);
805
806
                    copyRecursively(sr, targetFolder, javaMimeTypes, javaMimeTypesArr);
807
                }
808
            }
809
810
            if (!cosActive) {
811
                new FileOutputStream(tagFile).close();
812
            }
813
814
            if (keepResourceUpToDate)
815
                new FileOutputStream(tagUpdateResourcesFile).close();
816
        
817
            return true;
818
        }
819
        
820
        private Boolean performUpdate(@NonNull final Context ctx) throws IOException {
821
            final Iterable<? extends File> deleted = ctx.getDeleted();
822
            final Iterable<? extends File> updated = ctx.getUpdated();
823
            final boolean resource = ctx.isCopyResources();
824
            final File cacheRoot = ctx.getCacheRoot();
825
            if (!deleted.iterator().hasNext() && !updated.iterator().hasNext()) {
826
                return null;
827
            }
828
            File targetFolder = ctx.getTarget();
829
            if (targetFolder == null) {
830
                return null;
831
            }
832
            if (!isUpdateClasses(targetFolder)) {
833
                return null;
834
            }
835
836
            if (resource && !isUpdateResources(targetFolder)) {
837
                return null;
838
            }
839
        
840
            List<File> updatedFiles = new LinkedList<>();
841
842
            for (File deletedFile : deleted) {
843
                final String relPath = relativizeFile(cacheRoot, deletedFile);
844
                if (relPath == null) {
845
                    throw new IllegalArgumentException (String.format(
846
                        "Deleted file: %s is not under cache root: %s, (normalized file: %s).", //NOI18N
847
                        deletedFile.getAbsolutePath(),
848
                        cacheRoot.getAbsolutePath(),
849
                        FileUtil.normalizeFile(deletedFile).getAbsolutePath()));
850
                }
851
                File toDelete = resolveFile(targetFolder, relPath);
852
853
                toDelete.delete();
854
                updatedFiles.add(toDelete);
855
            }
856
857
            for (File updatedFile : updated) {
858
                final String relPath = relativizeFile(cacheRoot, updatedFile);
859
                if (relPath == null) {
860
                    throw new IllegalArgumentException (String.format(
861
                        "Updated file: %s is not under cache root: %s, (normalized file: %s).", //NOI18N
862
                        updatedFile.getAbsolutePath(),
863
                        cacheRoot.getAbsolutePath(),
864
                        FileUtil.normalizeFile(updatedFile).getAbsolutePath()));
865
                }
866
                File target = resolveFile(targetFolder, relPath);                        
867
868
                try {
869
                    copyFile(updatedFile, target);
870
                    updatedFiles.add(target);
871
                } catch (IOException ex) {
872
                    Exceptions.printStackTrace(ex);
873
                }
874
            }
875
            ctx.filesUpdated(updatedFiles);
876
            return true;
877
        }
878
        
879
        private boolean isUpdateClasses(@NullAllowed final File targetFolder) {
880
            if (targetFolder == null) {
881
                return false;
882
            }
883
            return new File(targetFolder, TAG_FILE_NAME).exists();
884
        }
885
        
886
        private boolean isUpdateResources(@NullAllowed final File targetFolder) {
887
            if (targetFolder == null) {
888
                return false;
889
            }
890
            return new File(targetFolder, TAG_UPDATE_RESOURCES).exists();
891
        }
892
    }
893
    
894
    @ServiceProvider(service = CompileOnSaveAction.Provider.class, position = Integer.MAX_VALUE)
895
    public static final class Provider implements CompileOnSaveAction.Provider {
896
        //@GuardedBy("normCache")
897
        private final Map<URL,Reference<DefaultCompileOnSaveAction>> normCache
898
                = new WeakHashMap<>();
899
        @Override
900
        public CompileOnSaveAction forRoot(@NonNull final URL root) {
901
            synchronized (normCache) {
902
                final Reference<DefaultCompileOnSaveAction> ref = normCache.get(root);
903
                DefaultCompileOnSaveAction res;
904
                if (ref == null || (res = ref.get()) == null) {
905
                    res = new DefaultCompileOnSaveAction(root);
906
                    normCache.put(root, new WeakReference<>(res));
907
                }
908
                return res;
909
            }
910
        }        
797
    }
911
    }
798
}
912
}

Return to bug 252595