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 270167
Collapse All | Expand All

(-)a/api.templates/apichanges.xml (+16 lines)
Lines 54-59 Link Here
54
<!-- ACTUAL CHANGES BEGIN HERE: -->
54
<!-- ACTUAL CHANGES BEGIN HERE: -->
55
55
56
  <changes>
56
  <changes>
57
      <change id="decorators">
58
          <api name="templates"/>
59
          <summary>Support for decorating the creation process</summary>
60
          <version major="1" minor="9"/>
61
          <date year="2017" month="3" day="22"/>
62
          <compatibility addition="yes" binary="compatible" source="compatible"/>
63
          <description>
64
              <p>
65
                  In addition to provide <code>CreateFromTemplateHandler</code> which overtakes the
66
                  template processing, <code>CreateFromTemplateDecorator</code> can participate in
67
                  file creation doing pre- and post-creation tasks and edits either in the created file
68
                  or other files.
69
              </p>
70
          </description>
71
          <class package="org.netbeans.api.templates" name="CreateFromTemplateDecorator"/>
72
      </change>
57
      <change id="mavenarchetypes">
73
      <change id="mavenarchetypes">
58
          <api name="templates"/>
74
          <api name="templates"/>
59
          <summary>Maven Archetypes</summary>
75
          <summary>Maven Archetypes</summary>
(-)a/api.templates/manifest.mf (-1 / +1 lines)
Lines 2-6 Link Here
2
AutoUpdate-Show-In-Client: false
2
AutoUpdate-Show-In-Client: false
3
OpenIDE-Module: org.netbeans.api.templates
3
OpenIDE-Module: org.netbeans.api.templates
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/templates/Bundle.properties
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/templates/Bundle.properties
5
OpenIDE-Module-Specification-Version: 1.8
5
OpenIDE-Module-Specification-Version: 1.9
6
OpenIDE-Module-Recommends: org.netbeans.templates.IndentEngine
6
OpenIDE-Module-Recommends: org.netbeans.templates.IndentEngine
(-)a/api.templates/src/org/netbeans/api/templates/CreateFromTemplateDecorator.java (+101 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright (c) 2017 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
package org.netbeans.api.templates;
41
42
import java.io.IOException;
43
import java.util.List;
44
import org.netbeans.api.annotations.common.CheckForNull;
45
import org.netbeans.api.annotations.common.NonNull;
46
import org.openide.filesystems.FileObject;
47
48
/**
49
 * Decorator for templating. The decorator will pre- or post-process the main file creation.
50
 * Decorators are called by the infrastructure, in the declaration order, before the template
51
 * file is processed and after it is processed, depending on {@link #isBeforeCreation()}
52
 * and {@link #isAfterCreation()} return values. A decorator may perform both pre- and post- 
53
 * processing.
54
 * <p/>
55
 * First the decorator is asked to {@link #accept} the creation process; if it does not,
56
 * it will not be invoked at all. Before/after main file creation the {@link #decorate}
57
 * method is called to perform its magic. Any reported additional files will be returned
58
 * as part of {@link FileBuilder#build()} result.
59
 * @since 1.9
60
 * @author sdedic
61
 */
62
public interface CreateFromTemplateDecorator {
63
    /**
64
     * Determines if the decorator should be called before template processing. Pre-decorators
65
     * can setup the environment before the main file creation takes place.
66
     * @return true, if the decorator should be called before template processing
67
     */
68
    public boolean  isBeforeCreation();
69
    
70
    /**
71
     * Determines if the decorator should be called after template processing. Post-decorators
72
     * can make additional (e.g. settings, registrations) adjustments.
73
     * @return true, if the decorator should be called after template processing
74
     */
75
    public boolean  isAfterCreation();
76
    
77
    /** 
78
     * Determines whether the decorator is willing to participate in creation.
79
     * If the decorator returns {@code false}, its {@link #decorate} will not be called. It
80
     * @param desc describes the request that is about to be performed
81
     * @return true if this decorator wants to participate in files creation
82
     */
83
    public abstract boolean accept(CreateDescriptor desc);
84
85
    /**
86
     * Extends the creation process. The decorator may alter the created file (it is the first in the 
87
     * returned list) or create additional files. In case it creates files, it must return list of the
88
     * added files.
89
     * <p/>
90
     * If the decorator is not interested, it should return simply {@code null}.
91
     * 
92
     * @param desc command objects that describes the file creation request
93
     * @param createdFiles  files created so far as part of the template creation
94
     * @return the newly create file(s)
95
     * @throws IOException if something goes wrong with I/O
96
     */
97
    public @CheckForNull List<FileObject> decorate(
98
            @NonNull CreateDescriptor    desc,
99
            @NonNull List<FileObject>    createdFiles
100
    ) throws IOException;
101
}
(-)a/api.templates/src/org/netbeans/api/templates/CreateFromTemplateImpl.java (-3 / +38 lines)
Lines 53-61 Link Here
53
import java.io.Writer;
53
import java.io.Writer;
54
import java.nio.charset.Charset;
54
import java.nio.charset.Charset;
55
import java.text.Format;
55
import java.text.Format;
56
import java.util.ArrayList;
56
import java.util.Collections;
57
import java.util.Collections;
57
import java.util.Date;
58
import java.util.Date;
58
import java.util.HashMap;
59
import java.util.HashMap;
60
import java.util.Iterator;
59
import java.util.List;
61
import java.util.List;
60
import java.util.Map;
62
import java.util.Map;
61
import javax.script.ScriptContext;
63
import javax.script.ScriptContext;
Lines 82-87 Link Here
82
    private final CreateDescriptor desc;
84
    private final CreateDescriptor desc;
83
    private Map<String, ?> originalParams;
85
    private Map<String, ?> originalParams;
84
    
86
    
87
    private List<CreateFromTemplateDecorator> decorators;
88
    
85
    private CreateFromTemplateImpl(FileBuilder builder) {
89
    private CreateFromTemplateImpl(FileBuilder builder) {
86
        this.builder = builder;
90
        this.builder = builder;
87
        this.desc = builder.getDescriptor();
91
        this.desc = builder.getDescriptor();
Lines 97-102 Link Here
97
        flb.withParameters(impl.findTemplateParameters());
101
        flb.withParameters(impl.findTemplateParameters());
98
    }
102
    }
99
    
103
    
104
    private void setupDecorators() {
105
        decorators = new ArrayList<>(Lookup.getDefault().lookupAll(CreateFromTemplateDecorator.class));
106
        for (Iterator<CreateFromTemplateDecorator> it = decorators.iterator(); it.hasNext(); ) {
107
            CreateFromTemplateDecorator dec = it.next();
108
            if (!dec.accept(desc)) {
109
                it.remove();
110
            }
111
        }
112
    }
113
    
100
    List<FileObject> build() throws IOException {
114
    List<FileObject> build() throws IOException {
101
        // side effects: replaces the map in CreateDescriptor
115
        // side effects: replaces the map in CreateDescriptor
102
        try {
116
        try {
Lines 113-120 Link Here
113
            }
127
            }
114
            // also modifies desc.getParameters, result not needed.
128
            // also modifies desc.getParameters, result not needed.
115
            findTemplateParameters();
129
            findTemplateParameters();
130
            setupDecorators();
116
            computeEffectiveName(desc);
131
            computeEffectiveName(desc);
117
132
133
            List<FileObject> initialFiles = callDecorators(true, new ArrayList<>());
118
            List<FileObject> pf = null;
134
            List<FileObject> pf = null;
119
            for (CreateFromTemplateHandler h : Lookup.getDefault().lookupAll(CreateFromTemplateHandler.class)) {
135
            for (CreateFromTemplateHandler h : Lookup.getDefault().lookupAll(CreateFromTemplateHandler.class)) {
120
                if (h.accept(desc)) {
136
                if (h.accept(desc)) {
Lines 124-145 Link Here
124
                }
140
                }
125
            }
141
            }
126
            // side effects from findTemplateParameters still in effect...
142
            // side effects from findTemplateParameters still in effect...
127
            if (pf != null || defaultMode == FileBuilder.Mode.FAIL) {
143
            if (pf == null && defaultMode != FileBuilder.Mode.FAIL) {
144
                pf = Collections.singletonList(defaultCreate());
145
            }
146
            if (pf == null) {
128
                return pf;
147
                return pf;
129
            }
148
            }
130
            return Collections.singletonList(defaultCreate());
149
            List<FileObject> result = new ArrayList<>(pf);
150
            result.addAll(initialFiles);
151
            callDecorators(false, result);
152
            return result;
131
        } finally {
153
        } finally {
132
            // bring back the parameters
154
            // bring back the parameters
133
            builder.getDescriptor().parameters = (Map<String, Object>)originalParams;
155
            builder.getDescriptor().parameters = (Map<String, Object>)originalParams;
134
        }
156
        }
135
    }
157
    }
136
    
158
    
159
    private List<FileObject>    callDecorators(boolean preCreate, List<FileObject> result) throws IOException {
160
        for (CreateFromTemplateDecorator deco : decorators) {
161
            if ((preCreate ? deco.isBeforeCreation() : deco.isAfterCreation())) {
162
                List<FileObject>  preFiles = deco.decorate(desc, result);
163
                if (preFiles != null) {
164
                    preFiles.removeAll(result);
165
                    result.addAll(preFiles);
166
                }
167
            }
168
        }
169
        return result;
170
    }
171
    
137
    /* package private */ static void computeEffectiveName(CreateDescriptor desc) {
172
    /* package private */ static void computeEffectiveName(CreateDescriptor desc) {
138
        String name = desc.getName();
173
        String name = desc.getName();
139
        if (name == null) {
174
        if (name == null) {
140
            // name is not set - try to check parameters, if some template attribute handler
175
            // name is not set - try to check parameters, if some template attribute handler
141
            // did not supply a suggestion:
176
            // did not supply a suggestion:
142
            Object o = desc.getParameters().get("name");
177
            Object o = desc.getParameters().get("name"); // NOi18N
143
            if (o instanceof String) {
178
            if (o instanceof String) {
144
                name = (String)o;
179
                name = (String)o;
145
            } else {
180
            } else {
(-)a/api.templates/test/unit/src/org/netbeans/modules/templates/CreateFromTemplateDecoratorTest.java (+227 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright (c) 2017 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
package org.netbeans.modules.templates;
41
42
import java.io.IOException;
43
import java.util.ArrayList;
44
import java.util.Collections;
45
import java.util.List;
46
import java.util.Map;
47
import java.util.concurrent.Callable;
48
import org.netbeans.api.templates.CreateDescriptor;
49
import org.netbeans.api.templates.CreateFromTemplateDecorator;
50
import org.netbeans.api.templates.FileBuilder;
51
import org.netbeans.junit.NbTestCase;
52
import org.openide.filesystems.FileObject;
53
import org.openide.filesystems.FileUtil;
54
import org.openide.loaders.DataFolder;
55
import org.openide.loaders.DataObject;
56
import org.openide.util.test.MockLookup;
57
58
/**
59
 *
60
 * @author sdedic
61
 */
62
public class CreateFromTemplateDecoratorTest extends NbTestCase {
63
64
    public CreateFromTemplateDecoratorTest(String name) {
65
        super(name);
66
    }
67
    
68
    private Deco decorator = new Deco();
69
    private FileObject root;
70
    private FileObject fo;
71
    private FileObject target;
72
    private DataFolder folder;
73
    private DataObject obj;
74
    
75
    @Override
76
    protected void setUp() throws Exception {
77
        super.setUp();
78
        MockLookup.init();
79
        root = FileUtil.createMemoryFileSystem().getRoot();
80
        fo = FileUtil.createData(root, "simpleObject.txt");
81
        folder = DataFolder.findFolder(target = FileUtil.createFolder(root, "target"));
82
        obj = DataObject.find(fo);
83
    }
84
    
85
    private boolean decorated;
86
    private int decoCount;
87
    
88
    private List<FileObject> created = new ArrayList<>();
89
    
90
    public void testPreCreateDecorator() throws Exception {
91
        MockLookup.setLayersAndInstances(new Deco() {
92
            @Override
93
            public boolean isBeforeCreation() {
94
                return true;
95
            }
96
            
97
            @Override
98
            public List<FileObject> decorate(CreateDescriptor desc, List<FileObject> createdFiles) throws IOException {
99
                assertSize("No files should have been created", createdFiles, 0);
100
                decorated = true;
101
                assertEquals(0, target.getChildren().length);
102
                return null;
103
            }
104
        });
105
        Map<String,String> parameters = Collections.singletonMap("type", "empty");
106
        DataObject n = obj.createFromTemplate(folder, "complex", parameters);
107
        assertTrue(decorated);
108
        assertNotNull(n);
109
    }
110
    
111
    public void testPostDecorator() throws Exception {
112
        MockLookup.setLayersAndInstances(new Deco() {
113
            @Override
114
            public boolean isAfterCreation() {
115
                return true;
116
            }
117
            
118
            @Override
119
            public List<FileObject> decorate(CreateDescriptor desc, List<FileObject> createdFiles) throws IOException {
120
                assertEquals("Main files is not present", 1, createdFiles.size());
121
                decorated = true;
122
                assertEquals(1, target.getChildren().length);
123
                return null;
124
            }
125
        });
126
        Map<String,String> parameters = Collections.singletonMap("type", "empty");
127
        DataObject n = obj.createFromTemplate(folder, "complex", parameters);
128
        assertTrue(decorated);
129
        assertNotNull(n);
130
    }
131
132
    public void testDecoratorSeesPrecedingFiles() throws Exception {
133
        MockLookup.setLayersAndInstances(new Deco() {
134
            @Override
135
            public boolean isBeforeCreation() {
136
                return true;
137
            }
138
            
139
            @Override
140
            public List<FileObject> decorate(CreateDescriptor desc, List<FileObject> createdFiles) throws IOException {
141
                assertEquals("No files should have been created", 0, createdFiles.size());
142
                decoCount++;
143
                assertEquals(0, folder.getPrimaryFile().getChildren().length);
144
                FileObject f = FileUtil.createData(target, "sideEffect1.txt");
145
                created.add(f);
146
                return Collections.singletonList(f);
147
            }
148
        }, new Deco() {
149
            @Override
150
            public boolean isBeforeCreation() {
151
                return true;
152
            }
153
            
154
            @Override
155
            public List<FileObject> decorate(CreateDescriptor desc, List<FileObject> createdFiles) throws IOException {
156
                decoCount++;
157
                assertEquals(1, target.getChildren().length);
158
                assertEquals(created, createdFiles);
159
                FileObject f = FileUtil.createData(target, "sideEffect2.txt");
160
                created.add(f);
161
                return Collections.singletonList(f);
162
            }
163
        },
164
        new Deco() {
165
            @Override
166
            public boolean isAfterCreation() {
167
                return true;
168
            }
169
            
170
            @Override
171
            public List<FileObject> decorate(CreateDescriptor desc, List<FileObject> createdFiles) throws IOException {
172
                created.add(0, folder.getPrimaryFile().getFileObject("n.txt"));
173
                decoCount++;
174
                assertEquals(3, target.getChildren().length);
175
                FileObject f = FileUtil.createData(target, "sideEffect3.txt");
176
                created.add(f);
177
                return Collections.singletonList(f);
178
            }
179
        }, new Deco() {
180
            @Override
181
            public boolean isAfterCreation() {
182
                return true;
183
            }
184
            
185
            @Override
186
            public List<FileObject> decorate(CreateDescriptor desc, List<FileObject> createdFiles) throws IOException {
187
                decoCount++;
188
                assertEquals(4,target.getChildren().length);
189
                assertEquals(created, createdFiles);
190
                FileObject f = FileUtil.createData(target, "sideEffect4.txt");
191
                created.add(f);
192
                return Collections.singletonList(f);
193
            }
194
            
195
        });
196
        List<FileObject> fos = new FileBuilder(fo, target).name("n").build();
197
        assertEquals(5, fos.size());
198
        assertEquals(created, fos);
199
        assertEquals(4, decoCount);
200
    }
201
    
202
    static class Deco implements CreateFromTemplateDecorator {
203
        boolean before;
204
        boolean after;
205
        Callable<Void> callback;
206
        
207
        @Override
208
        public boolean isBeforeCreation() {
209
            return before;
210
        }
211
212
        @Override
213
        public boolean isAfterCreation() {
214
            return after;
215
        }
216
217
        @Override
218
        public boolean accept(CreateDescriptor desc) {
219
            return true;
220
        }
221
222
        @Override
223
        public List<FileObject> decorate(CreateDescriptor desc, List<FileObject> createdFiles) throws IOException {
224
            return null;
225
        }
226
    }
227
}
(-)a/java.api.common/nbproject/project.xml (+8 lines)
Lines 98-103 Link Here
98
                    </run-dependency>
98
                    </run-dependency>
99
                </dependency>
99
                </dependency>
100
                <dependency>
100
                <dependency>
101
                    <code-name-base>org.netbeans.api.templates</code-name-base>
102
                    <build-prerequisite/>
103
                    <compile-dependency/>
104
                    <run-dependency>
105
                        <specification-version>1.9</specification-version>
106
                    </run-dependency>
107
                </dependency>
108
                <dependency>
101
                    <code-name-base>org.netbeans.libs.antlr3.runtime</code-name-base>
109
                    <code-name-base>org.netbeans.libs.antlr3.runtime</code-name-base>
102
                    <build-prerequisite/>
110
                    <build-prerequisite/>
103
                    <compile-dependency/>
111
                    <compile-dependency/>
(-)a/java.api.common/src/org/netbeans/modules/java/api/common/impl/DefaultProjectModulesModifier.java (+6 lines)
Lines 182-187 Link Here
182
                .map((url) -> SourceUtils.getModuleName(url, true))
182
                .map((url) -> SourceUtils.getModuleName(url, true))
183
                .filter((name) -> name != null)
183
                .filter((name) -> name != null)
184
                .collect(Collectors.toList());
184
                .collect(Collectors.toList());
185
        return addRequiredModules(info, moduleNames);
186
    }
187
    
188
    public static boolean addRequiredModules(
189
            @NonNull final FileObject info,
190
            @NonNull final  Collection<String> moduleNames) throws IOException {
185
        if (moduleNames.isEmpty()) {
191
        if (moduleNames.isEmpty()) {
186
            return false;
192
            return false;
187
        }
193
        }
(-)a/java.api.common/src/org/netbeans/modules/java/api/common/impl/TemplateModuleDeclarator.java (+286 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright (c) 2017 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
package org.netbeans.modules.java.api.common.impl;
41
42
import com.sun.source.tree.AssignmentTree;
43
import com.sun.source.tree.MemberSelectTree;
44
import com.sun.source.tree.Tree;
45
import com.sun.source.tree.Tree.Kind;
46
import com.sun.source.util.TreePath;
47
import com.sun.source.util.TreePathScanner;
48
import java.io.IOException;
49
import java.util.ArrayList;
50
import java.util.Arrays;
51
import java.util.Collections;
52
import java.util.HashSet;
53
import java.util.List;
54
import java.util.Set;
55
import javax.lang.model.element.Element;
56
import javax.lang.model.element.ElementKind;
57
import javax.lang.model.element.PackageElement;
58
import javax.lang.model.type.TypeKind;
59
import javax.lang.model.type.TypeMirror;
60
import org.netbeans.api.java.classpath.ClassPath;
61
import org.netbeans.api.java.queries.SourceLevelQuery;
62
import org.netbeans.api.java.source.ClasspathInfo;
63
import org.netbeans.api.java.source.ClasspathInfo.PathKind;
64
import org.netbeans.api.java.source.CompilationController;
65
import org.netbeans.api.java.source.CompilationInfo;
66
import org.netbeans.api.java.source.JavaSource;
67
import org.netbeans.api.java.source.SourceUtils;
68
import org.netbeans.api.java.source.Task;
69
import org.netbeans.api.templates.CreateDescriptor;
70
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
71
import org.openide.filesystems.FileObject;
72
import org.openide.modules.SpecificationVersion;
73
import org.openide.util.lookup.ServiceProvider;
74
import org.netbeans.api.templates.CreateFromTemplateDecorator;
75
76
/**
77
 * Automatically declares dependencies on modules used in template.
78
 * Scans the created file; if some unresolved identifier appears, which corresponds to a 
79
 * class in platform or libraries, the decorator will add {@code requires} directive to the
80
 * appropriate {@code module-info.java}. Activates only on projects which have source level >= 9
81
 * and contain module declaration.
82
 * 
83
 * @author sdedic
84
 */
85
@ServiceProvider(service = CreateFromTemplateDecorator.class)
86
public final class TemplateModuleDeclarator implements CreateFromTemplateDecorator {
87
    private static final String NAME_MODULE_INFO = "module-info.java"; // NOI18N
88
    private static final String ATTRIBUTE_REQUIRED_MODULES = "requiredModules";                 // NOI18N
89
    private static final SpecificationVersion   SOURCE_LEVEL_9 = new SpecificationVersion("9"); // NOI18N
90
    private static final String MIME_JAVA = "text/x-java"; // NOI18N
91
    private static final String CLASS_EXTENSION = ".class"; // NOI18N
92
    
93
    @Override
94
    public boolean isBeforeCreation() {
95
        return false;
96
    }
97
98
    @Override
99
    public boolean isAfterCreation() {
100
        return true;
101
    }
102
103
    @Override
104
    public boolean accept(CreateDescriptor desc) {
105
        if (desc.getValue(ATTRIBUTE_REQUIRED_MODULES) != null) {
106
            return true;
107
        }
108
        FileObject t = desc.getTarget();
109
        if (!MIME_JAVA.equals(desc.getTemplate().getMIMEType())) {
110
            return false;
111
        }
112
        String s = SourceLevelQuery.getSourceLevel(t);
113
        if (s == null || SOURCE_LEVEL_9.compareTo(new SpecificationVersion(s)) > 0) {
114
            return false;
115
        }
116
        // check module-info
117
        ClassPath srcPath = ClassPath.getClassPath(t, ClassPath.SOURCE);
118
        if (srcPath == null) {
119
            return false;
120
        }
121
        if (srcPath.findResource(NAME_MODULE_INFO) == null) { // NOI18N
122
            return false;
123
        }
124
        return true;
125
    }
126
127
    @Override
128
    public List<FileObject> decorate(CreateDescriptor desc, List<FileObject> createdFiles) throws IOException {
129
        List<FileObject>  jsources = new ArrayList<>(createdFiles.size());
130
        for (FileObject f : createdFiles) {
131
            if (MIME_JAVA.equals(f.getMIMEType())) {
132
                jsources.add(f);
133
            }
134
        }
135
        if (jsources.isEmpty()) {
136
            return null;
137
        }
138
        FileObject t = desc.getTarget();
139
        // check module-info
140
        ClassPath srcPath = ClassPath.getClassPath(t, ClassPath.SOURCE);
141
        if (srcPath == null) {
142
            return null;
143
        }
144
        FileObject modinfo = srcPath.findResource(NAME_MODULE_INFO); // NOI18N
145
        if (modinfo == null) {
146
            return null;
147
        }
148
        
149
        Object a = desc.getTemplate().getAttribute(ATTRIBUTE_REQUIRED_MODULES);
150
        Set<String> modules;
151
        
152
        if (a instanceof String) {
153
            String s = a.toString();
154
            if (!s.isEmpty()) {
155
                modules = new HashSet<>(Arrays.asList(s.split(","))); // NOI18N
156
            } else {
157
                modules = Collections.emptySet();
158
            }
159
        } else {
160
            modules = inferModuleNames(t, jsources);
161
        }
162
        
163
        if (modules.isEmpty()) {
164
            return null;
165
        }
166
        DefaultProjectModulesModifier.addRequiredModules(modinfo, modules);
167
        return null;
168
    }
169
        
170
   private Set<String> inferModuleNames(FileObject t, List<FileObject> jsources) throws IOException {
171
        ClasspathInfo cpi = ClasspathInfo.create(t);
172
        JavaSource src = JavaSource.create(cpi, jsources);
173
        Set<String> unresolved = new HashSet<>();
174
        src.runUserActionTask(new Task<CompilationController>() {
175
            @Override
176
            public void run(CompilationController parameter) throws Exception {
177
                parameter.toPhase(JavaSource.Phase.RESOLVED);
178
                Scanner s = new Scanner(parameter, unresolved);
179
                s.scan(parameter.getCompilationUnit(), null);
180
            }
181
        }, true);
182
        ClassPath searchPath = ClassPathSupport.createProxyClassPath(
183
                cpi.getClassPath(PathKind.MODULE_BOOT),
184
                cpi.getClassPath(PathKind.MODULE_COMPILE));
185
        Set<String> moduleNames = new HashSet<>();
186
        for (String fqn : unresolved) {
187
            findModuleNames(fqn, searchPath, moduleNames);
188
        }
189
        return moduleNames;
190
    }
191
   
192
    
193
    private void findModuleNames(String fqn, ClassPath searchPath, Set<String> moduleNames) {
194
        fqn = fqn.replace(".", "/");
195
        String resourceName = fqn + CLASS_EXTENSION;
196
        FileObject classResource = searchPath.findResource(resourceName);
197
        if (classResource == null || classResource.isFolder()) {
198
            int last = fqn.length();
199
            for (int i = fqn.lastIndexOf('.'); i >= 0; i = fqn.lastIndexOf('/', last)) { // NOI18N
200
                resourceName = fqn.substring(0, i);
201
                classResource = searchPath.findResource(resourceName + CLASS_EXTENSION);
202
                if (classResource != null && classResource.isData()) {
203
                    resourceName += fqn.substring(i).replace("/", "$") + CLASS_EXTENSION; // NOI18N
204
                    classResource = searchPath.findResource(resourceName);
205
                    break;
206
                }
207
                classResource = searchPath.findResource(resourceName);
208
                if (classResource != null && classResource.isFolder()) {
209
                    return;
210
                }
211
            }
212
        }
213
        if (classResource != null) {
214
            FileObject r = searchPath.findOwnerRoot(classResource);
215
            for (ClassPath.Entry e : searchPath.entries()) {
216
                if (e.getRoot().equals(r)) {
217
                    String name = SourceUtils.getModuleName(e.getURL(), true);
218
                    moduleNames.add(name);
219
                }
220
            }
221
        }
222
    }
223
   
224
    private static class Scanner extends TreePathScanner<Boolean, Boolean> {
225
        private final CompilationInfo info;
226
        private final Set<String> unresolved;
227
        
228
        public Scanner(CompilationInfo info, Set<String> s) {
229
            this.info = info;
230
            this.unresolved = s;
231
        }
232
233
        @Override
234
        public Boolean visitMemberSelect(MemberSelectTree node, Boolean p) {
235
            Boolean r = super.scan(node.getExpression(), p);
236
            if (r) {
237
                return r;
238
            }
239
            TreePath parentPath = getCurrentPath().getParentPath();
240
            if (parentPath == null) {
241
                return null;
242
            }
243
            Tree t = node;
244
            Tree par = parentPath.getLeaf();
245
            if (node.getIdentifier().contentEquals("*")) { // NOI18N
246
                // assume import, discard one level
247
                t = node.getExpression();
248
                par = node;
249
                parentPath = new TreePath(parentPath, t);
250
            }
251
            if (par.getKind() == Kind.METHOD_INVOCATION) {
252
                return null;
253
            }
254
            final Element el = info.getTrees().getElement(getCurrentPath());
255
            if (el == null || !(el.getKind().isClass() || el.getKind().isInterface() || el.getKind() == ElementKind.PACKAGE)) {
256
                return true;
257
            }
258
            TypeMirror type = el.asType();
259
            if (type == null) {
260
                return null;
261
            }
262
            String fqn = null;
263
            
264
            if (type.getKind() == TypeKind.ERROR) {
265
                if (par.getKind() == Tree.Kind.ASSIGNMENT) {
266
                    AssignmentTree at = (AssignmentTree) getCurrentPath().getParentPath().getLeaf();
267
268
                    if (at.getVariable() == node) {
269
                        return null;
270
                    }
271
                }
272
            } else if (type.getKind() == TypeKind.PACKAGE) {
273
                String s = ((PackageElement) el).getQualifiedName().toString();
274
                if (info.getElements().getPackageElement(s) != null) {
275
                    return null;
276
                }
277
            }
278
            fqn = node.toString();
279
            if (fqn.endsWith(".<error>")) { // NOI18N
280
                fqn = fqn.substring(0, fqn.lastIndexOf(".")); // NOI18N
281
            }
282
            unresolved.add(fqn);
283
            return null;
284
        }
285
    }
286
}
(-)a/java.project/nbproject/project.xml (+8 lines)
Lines 77-82 Link Here
77
                    </run-dependency>
77
                    </run-dependency>
78
                </dependency>
78
                </dependency>
79
                <dependency>
79
                <dependency>
80
                    <code-name-base>org.netbeans.api.templates</code-name-base>
81
                    <build-prerequisite/>
82
                    <compile-dependency/>
83
                    <run-dependency>
84
                        <specification-version>1.8</specification-version>
85
                    </run-dependency>
86
                </dependency>
87
                <dependency>
80
                    <code-name-base>org.netbeans.modules.classfile</code-name-base>
88
                    <code-name-base>org.netbeans.modules.classfile</code-name-base>
81
                    <build-prerequisite/>
89
                    <build-prerequisite/>
82
                    <compile-dependency/>
90
                    <compile-dependency/>

Return to bug 270167