Line 0
Link Here
|
|
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 2013 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 2013 Sun Microsystems, Inc. |
41 |
*/ |
42 |
|
43 |
package org.netbeans.modules.java.api.common.project.ui; |
44 |
|
45 |
import java.awt.Color; |
46 |
import java.awt.Image; |
47 |
import java.beans.PropertyChangeEvent; |
48 |
import java.beans.PropertyChangeListener; |
49 |
import java.io.CharConversionException; |
50 |
import java.net.URL; |
51 |
import javax.swing.Action; |
52 |
import javax.swing.Icon; |
53 |
import javax.swing.UIManager; |
54 |
import javax.swing.event.ChangeEvent; |
55 |
import javax.swing.event.ChangeListener; |
56 |
import org.netbeans.api.annotations.common.NonNull; |
57 |
import org.netbeans.api.annotations.common.NullAllowed; |
58 |
import org.netbeans.api.java.project.JavaProjectConstants; |
59 |
import org.netbeans.api.project.FileOwnerQuery; |
60 |
import org.netbeans.api.project.Project; |
61 |
import org.netbeans.api.project.ProjectInformation; |
62 |
import org.netbeans.api.project.ProjectManager; |
63 |
import org.netbeans.api.project.ProjectUtils; |
64 |
import org.netbeans.api.project.SourceGroup; |
65 |
import org.netbeans.api.project.ui.ProjectProblems; |
66 |
import org.netbeans.spi.java.project.support.ui.PackageView; |
67 |
import org.netbeans.spi.project.support.ant.PropertyEvaluator; |
68 |
import org.netbeans.spi.project.ui.ProjectProblemsProvider; |
69 |
import org.netbeans.spi.project.ui.support.CommonProjectActions; |
70 |
import org.netbeans.spi.project.ui.support.DefaultProjectOperations; |
71 |
import org.netbeans.spi.project.ui.support.NodeFactorySupport; |
72 |
import org.openide.filesystems.FileObject; |
73 |
import org.openide.filesystems.FileUtil; |
74 |
import org.openide.loaders.DataObject; |
75 |
import org.openide.loaders.DataObjectNotFoundException; |
76 |
import org.openide.nodes.AbstractNode; |
77 |
import org.openide.nodes.Node; |
78 |
import org.openide.util.ChangeSupport; |
79 |
import org.openide.util.HelpCtx; |
80 |
import org.openide.util.ImageUtilities; |
81 |
import org.openide.util.Lookup; |
82 |
import org.openide.util.NbBundle; |
83 |
import org.openide.util.Parameters; |
84 |
import org.openide.util.RequestProcessor; |
85 |
import org.openide.util.WeakListeners; |
86 |
import org.openide.util.lookup.AbstractLookup; |
87 |
import org.openide.util.lookup.InstanceContent; |
88 |
import org.openide.xml.XMLUtil; |
89 |
|
90 |
/** |
91 |
* Support for creating default {@link LogicalViewProvider2} for Ant Based Project. |
92 |
* @author Tomas Zezula |
93 |
* @since 1.62 |
94 |
*/ |
95 |
public final class LogicalViewProviders { |
96 |
|
97 |
private LogicalViewProviders() { |
98 |
throw new IllegalStateException("No instance allowed"); //NOI18N |
99 |
} |
100 |
|
101 |
|
102 |
/** |
103 |
* Creates a new {@link LogicalViewProviderBuilder}. |
104 |
* @param project the project for which the builder should be created |
105 |
* @param eval the project's {@link PropertyEvaluator} |
106 |
* @param extensionFolder the extension point name |
107 |
* @return the new {@link LogicalViewProviderBuilder} |
108 |
*/ |
109 |
@NonNull |
110 |
public static LogicalViewProviderBuilder createBuilder( |
111 |
@NonNull final Project project, |
112 |
@NonNull final PropertyEvaluator eval, |
113 |
@NonNull final String extensionFolder) { |
114 |
return new LogicalViewProviderBuilder( |
115 |
project, |
116 |
eval, |
117 |
extensionFolder); |
118 |
} |
119 |
|
120 |
|
121 |
/** |
122 |
* Builder for creating a new configured {@link LogicalViewProvider2} instance. |
123 |
*/ |
124 |
public static class LogicalViewProviderBuilder { |
125 |
|
126 |
private final Project project; |
127 |
private final PropertyEvaluator eval; |
128 |
private final String projectType; |
129 |
private HelpCtx helpContext; |
130 |
private CompileOnSaveBadge badgeStatus; |
131 |
|
132 |
|
133 |
private LogicalViewProviderBuilder( |
134 |
@NonNull final Project project, |
135 |
@NonNull final PropertyEvaluator eval, |
136 |
@NonNull final String projectType) { |
137 |
Parameters.notNull("project", project); //NOI18N |
138 |
Parameters.notNull("eval", eval); //NOI18N |
139 |
Parameters.notNull("projectType", projectType); //NOI18N |
140 |
this.project = project; |
141 |
this.eval = eval; |
142 |
this.projectType = projectType; |
143 |
} |
144 |
|
145 |
/** |
146 |
* Sets a {@link HelpCtx}. |
147 |
* @param helpContext the root node {@link HelpCtx} |
148 |
* @return the {@link LogicalViewProviderBuilder} |
149 |
*/ |
150 |
@NonNull |
151 |
public LogicalViewProviderBuilder setHelpCtx(@NonNull final HelpCtx helpContext) { |
152 |
Parameters.notNull("helpContext", helpContext); //NOI18N |
153 |
this.helpContext = helpContext; |
154 |
return this; |
155 |
} |
156 |
|
157 |
/** |
158 |
* Sets a compile on save badge status. |
159 |
* @param badgeStatus the compile on save badge status |
160 |
* @return the {@link LogicalViewProviderBuilder} |
161 |
*/ |
162 |
@NonNull |
163 |
public LogicalViewProviderBuilder setCompileOnSaveBadge(@NonNull final CompileOnSaveBadge badgeStatus) { |
164 |
Parameters.notNull("badgeStatus", badgeStatus); //NOI18N |
165 |
this.badgeStatus = badgeStatus; |
166 |
return this; |
167 |
} |
168 |
|
169 |
/** |
170 |
* Creates a new configured {@link LogicalViewProvider2} instance. |
171 |
* @return the {@link LogicalViewProvider2} instance |
172 |
*/ |
173 |
@NonNull |
174 |
public LogicalViewProvider2 build() { |
175 |
return new LogicalViewProviderImpl( |
176 |
project, |
177 |
eval, |
178 |
projectType, |
179 |
helpContext, |
180 |
badgeStatus); |
181 |
} |
182 |
|
183 |
} |
184 |
|
185 |
|
186 |
/** |
187 |
* Compile on Save badge status. |
188 |
* The {@link CompileOnSaveBadge} is used by {@link LogicalViewProvider2} |
189 |
* to enable or disable the project badge notifying an user about disabled |
190 |
* compile on save. |
191 |
*/ |
192 |
public static interface CompileOnSaveBadge { |
193 |
/** |
194 |
* Badge visibility check. |
195 |
* @return true if the badge should be visible |
196 |
*/ |
197 |
boolean isBadgeVisible(); |
198 |
/** |
199 |
* Checks if the changed property affects the compile on save visibility. |
200 |
* @param propertyName the name of changed property |
201 |
* @return true if compile on save badge should be recalculated |
202 |
*/ |
203 |
boolean isImportant(@NonNull String propertyName); |
204 |
} |
205 |
|
206 |
|
207 |
private static class LogicalViewProviderImpl implements LogicalViewProvider2 { |
208 |
|
209 |
private static final RequestProcessor RP = new RequestProcessor(LogicalViewProviders.class); |
210 |
private static final String COMPILE_ON_SAVE_DISABLED_BADGE_PATH = "org/netbeans/modules/java/api/common/project/ui/resources/compileOnSaveDisabledBadge.gif"; //NOI18N |
211 |
private static final Image compileOnSaveDisabledBadge; |
212 |
static { |
213 |
URL errorBadgeIconURL = LogicalViewProviders.class.getClassLoader().getResource(COMPILE_ON_SAVE_DISABLED_BADGE_PATH); |
214 |
String compileOnSaveDisabledTP = "<img src=\"" + errorBadgeIconURL + "\"> " + NbBundle.getMessage(LogicalViewProviders.class, "TP_CompileOnSaveDisabled"); |
215 |
compileOnSaveDisabledBadge = ImageUtilities.assignToolTipToImage(ImageUtilities.loadImage(COMPILE_ON_SAVE_DISABLED_BADGE_PATH), compileOnSaveDisabledTP); |
216 |
} |
217 |
|
218 |
private final Project project; |
219 |
private final PropertyEvaluator evaluator; |
220 |
private final String projectType; |
221 |
private final HelpCtx helpContext; |
222 |
private final CompileOnSaveBadge badgeStatus; |
223 |
private final ChangeSupport changeSupport = new ChangeSupport(this); |
224 |
private final PropertyChangeListener pcl; |
225 |
private final RequestProcessor.Task task = RP.create(new Runnable() { |
226 |
public @Override void run() { |
227 |
setBroken(ProjectProblems.isBroken(project)); |
228 |
setCompileOnSaveDisabled(isCompileOnSaveDisabled()); |
229 |
} |
230 |
}); |
231 |
|
232 |
private volatile boolean listenersInited; |
233 |
private volatile boolean broken; //Represents a state where project has a broken reference repairable by broken reference support |
234 |
private volatile boolean compileOnSaveDisabled; //true iff Compile-on-Save is disabled |
235 |
|
236 |
|
237 |
public LogicalViewProviderImpl( |
238 |
@NonNull final Project project, |
239 |
@NonNull final PropertyEvaluator evaluator, |
240 |
@NonNull final String projectType, |
241 |
@NullAllowed final HelpCtx helpContext, |
242 |
@NullAllowed final CompileOnSaveBadge badgeStatus) { |
243 |
Parameters.notNull("project", project); //NOI18N |
244 |
Parameters.notNull("evaluator", evaluator); //NOI18N |
245 |
Parameters.notNull("projectType", projectType); //NOI18N |
246 |
this.project = project; |
247 |
this.evaluator = evaluator; |
248 |
this.projectType = projectType; |
249 |
this.helpContext = helpContext; |
250 |
this.badgeStatus = badgeStatus; |
251 |
pcl = new PropertyChangeListener() { |
252 |
@Override |
253 |
public void propertyChange(PropertyChangeEvent evt) { |
254 |
final String propName = evt.getPropertyName(); |
255 |
if (ProjectProblemsProvider.PROP_PROBLEMS.equals(evt.getPropertyName()) || |
256 |
(badgeStatus != null && (propName == null || badgeStatus.isImportant(propName)))) { |
257 |
testBroken(); |
258 |
} |
259 |
} |
260 |
}; |
261 |
} |
262 |
|
263 |
private void initListeners() { |
264 |
if (listenersInited) { |
265 |
return; |
266 |
} |
267 |
ProjectManager.mutex().readAccess(new Runnable() { |
268 |
@Override |
269 |
public void run() { |
270 |
synchronized (LogicalViewProviderImpl.class) { |
271 |
if (!listenersInited) { |
272 |
evaluator.addPropertyChangeListener(pcl); |
273 |
final ProjectProblemsProvider ppp = project.getLookup().lookup(ProjectProblemsProvider.class); |
274 |
if (ppp != null) { |
275 |
ppp.addPropertyChangeListener(pcl); |
276 |
} |
277 |
listenersInited = true; |
278 |
} |
279 |
} |
280 |
} |
281 |
}); |
282 |
} |
283 |
|
284 |
@Override |
285 |
public Node createLogicalView() { |
286 |
initListeners(); |
287 |
final InstanceContent ic = new InstanceContent(); |
288 |
ic.add(project); |
289 |
ic.add(project, new InstanceContent.Convertor<Project, FileObject>() { |
290 |
@Override |
291 |
public FileObject convert(Project obj) { |
292 |
return obj.getProjectDirectory(); |
293 |
} |
294 |
@Override |
295 |
public Class<? extends FileObject> type(Project obj) { |
296 |
return FileObject.class; |
297 |
} |
298 |
@Override |
299 |
public String id(Project obj) { |
300 |
final FileObject fo = obj.getProjectDirectory(); |
301 |
return fo == null ? "" : fo.getPath(); //NOI18N |
302 |
} |
303 |
@Override |
304 |
public String displayName(Project obj) { |
305 |
return obj.toString(); |
306 |
} |
307 |
}); |
308 |
ic.add(project, new InstanceContent.Convertor<Project, DataObject>() { |
309 |
@Override |
310 |
public DataObject convert(Project obj) { |
311 |
try { |
312 |
final FileObject fo = obj.getProjectDirectory(); |
313 |
return fo == null ? null : DataObject.find(fo); |
314 |
} catch (DataObjectNotFoundException ex) { |
315 |
return null; |
316 |
} |
317 |
} |
318 |
@Override |
319 |
public Class<? extends DataObject> type(Project obj) { |
320 |
return DataObject.class; |
321 |
} |
322 |
@Override |
323 |
public String id(Project obj) { |
324 |
final FileObject fo = obj.getProjectDirectory(); |
325 |
return fo == null ? "" : fo.getPath(); //NOI18N |
326 |
} |
327 |
@Override |
328 |
public String displayName(Project obj) { |
329 |
return obj.toString(); |
330 |
} |
331 |
}); |
332 |
return new LogicalViewRootNode(new AbstractLookup(ic)); |
333 |
} |
334 |
|
335 |
@Override |
336 |
public Node findPath(Node root, Object target) { |
337 |
Project prj = root.getLookup().lookup(Project.class); |
338 |
if (prj == null) { |
339 |
return null; |
340 |
} |
341 |
|
342 |
if (target instanceof FileObject) { |
343 |
FileObject fo = (FileObject) target; |
344 |
if (isOtherProjectSource(fo, prj)) { |
345 |
return null; // Don't waste time if project does not own the fo among sources |
346 |
} |
347 |
|
348 |
for (Node n : root.getChildren().getNodes(true)) { |
349 |
Node result = PackageView.findPath(n, target); |
350 |
if (result != null) { |
351 |
return result; |
352 |
} |
353 |
} |
354 |
} |
355 |
|
356 |
return null; |
357 |
} |
358 |
|
359 |
private static boolean isOtherProjectSource( |
360 |
@NonNull final FileObject fo, |
361 |
@NonNull final Project me) { |
362 |
final Project owner = FileOwnerQuery.getOwner(fo); |
363 |
if (owner == null) { |
364 |
return false; |
365 |
} |
366 |
if (me.equals(owner)) { |
367 |
return false; |
368 |
} |
369 |
for (SourceGroup sg : ProjectUtils.getSources(owner).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA)) { |
370 |
if (FileUtil.isParentOf(sg.getRootFolder(), fo)) { |
371 |
return true; |
372 |
} |
373 |
} |
374 |
return false; |
375 |
} |
376 |
|
377 |
public void addChangeListener(ChangeListener l) { |
378 |
changeSupport.addChangeListener(l); |
379 |
} |
380 |
|
381 |
public void removeChangeListener(ChangeListener l) { |
382 |
changeSupport.removeChangeListener(l); |
383 |
} |
384 |
|
385 |
@Override |
386 |
public void testBroken() { |
387 |
task.schedule(500); |
388 |
} |
389 |
|
390 |
private boolean isCompileOnSaveDisabled() { |
391 |
return badgeStatus != null && badgeStatus.isBadgeVisible(); |
392 |
} |
393 |
|
394 |
private final class LogicalViewRootNode extends AbstractNode implements ChangeListener, PropertyChangeListener { |
395 |
|
396 |
private final ProjectInformation info; |
397 |
|
398 |
@SuppressWarnings("LeakingThisInConstructor") |
399 |
LogicalViewRootNode(@NonNull final Lookup lkp) { |
400 |
super(NodeFactorySupport.createCompositeChildren( |
401 |
project, |
402 |
String.format("Projects/%s/Nodes",projectType)), //NOI18N |
403 |
lkp); |
404 |
broken = ProjectProblems.isBroken(project); |
405 |
compileOnSaveDisabled = isCompileOnSaveDisabled(); |
406 |
addChangeListener(WeakListeners.change(this, LogicalViewProviderImpl.this)); |
407 |
final ProjectInformation pi = project.getLookup().lookup(ProjectInformation.class); |
408 |
info = pi != null ? pi : new SimpleInfo(project); |
409 |
info.addPropertyChangeListener(WeakListeners.propertyChange(this, info)); |
410 |
} |
411 |
|
412 |
@Override |
413 |
public String getShortDescription() { |
414 |
String prjDirDispName = FileUtil.getFileDisplayName(project.getProjectDirectory()); |
415 |
return NbBundle.getMessage(LogicalViewProviderImpl.class, "HINT_project_root_node", prjDirDispName); |
416 |
} |
417 |
|
418 |
@Override |
419 |
public String getHtmlDisplayName() { |
420 |
String dispName = super.getDisplayName(); |
421 |
try { |
422 |
dispName = XMLUtil.toElementContent(dispName); |
423 |
} catch (CharConversionException ex) { |
424 |
return dispName; |
425 |
} |
426 |
return broken ? "<font color=\"#"+Integer.toHexString(getErrorForeground().getRGB() & 0xffffff) +"\">" + dispName + "</font>" : null; //NOI18N |
427 |
} |
428 |
|
429 |
@Override |
430 |
public void stateChanged(ChangeEvent e) { |
431 |
fireIconChange(); |
432 |
fireOpenedIconChange(); |
433 |
fireDisplayNameChange(null, null); |
434 |
} |
435 |
|
436 |
@Override |
437 |
public void propertyChange(PropertyChangeEvent evt) { |
438 |
RP.post(new Runnable() { |
439 |
public @Override void run() { |
440 |
fireNameChange(null, null); |
441 |
fireDisplayNameChange(null, null); |
442 |
} |
443 |
}); |
444 |
} |
445 |
|
446 |
@Override |
447 |
public Action[] getActions( boolean context ) { |
448 |
return CommonProjectActions.forType(projectType); |
449 |
} |
450 |
|
451 |
@Override |
452 |
public boolean canRename() { |
453 |
return true; |
454 |
} |
455 |
|
456 |
@Override |
457 |
public String getName() { |
458 |
return info.getDisplayName(); |
459 |
} |
460 |
|
461 |
@Override |
462 |
public void setName(String s) { |
463 |
DefaultProjectOperations.performDefaultRenameOperation(project, s); |
464 |
} |
465 |
|
466 |
@Override |
467 |
public Image getIcon(int type) { |
468 |
final Icon icon = info.getIcon(); |
469 |
final Image img = icon == null ? |
470 |
super.getIcon(type) : |
471 |
ImageUtilities.icon2Image(icon); |
472 |
return !broken && compileOnSaveDisabled ? |
473 |
ImageUtilities.mergeImages(img, compileOnSaveDisabledBadge, 8, 0) : |
474 |
img; |
475 |
} |
476 |
|
477 |
@Override |
478 |
public Image getOpenedIcon(int type) { |
479 |
return getIcon(type); |
480 |
} |
481 |
|
482 |
@Override |
483 |
public HelpCtx getHelpCtx() { |
484 |
return helpContext == null ? |
485 |
super.getHelpCtx() : |
486 |
helpContext; |
487 |
} |
488 |
|
489 |
} |
490 |
|
491 |
// Private methods ------------------------------------------------- |
492 |
|
493 |
private void setBroken(boolean broken) { |
494 |
//Weak consistent, only visibility required |
495 |
if (this.broken != broken) { |
496 |
this.broken = broken; |
497 |
changeSupport.fireChange(); |
498 |
} |
499 |
} |
500 |
|
501 |
private void setCompileOnSaveDisabled (boolean value) { |
502 |
//Weak consistent, only visibility required |
503 |
if (this.compileOnSaveDisabled != value) { |
504 |
this.compileOnSaveDisabled = value; |
505 |
changeSupport.fireChange(); |
506 |
} |
507 |
} |
508 |
|
509 |
|
510 |
@NonNull |
511 |
private static Color getErrorForeground() { |
512 |
Color result = UIManager.getDefaults().getColor("nb.errorForeground"); //NOI18N |
513 |
if (result == null) { |
514 |
result = Color.RED; |
515 |
} |
516 |
return result; |
517 |
} |
518 |
} |
519 |
|
520 |
private static final class SimpleInfo implements ProjectInformation { |
521 |
|
522 |
private final Project project; |
523 |
|
524 |
SimpleInfo(@NonNull final Project project) { |
525 |
Parameters.notNull("project", project); //NOI18N |
526 |
this.project = project; |
527 |
} |
528 |
|
529 |
@Override |
530 |
public String getName() { |
531 |
return project.getProjectDirectory().getName(); |
532 |
} |
533 |
|
534 |
@Override |
535 |
public String getDisplayName() { |
536 |
return getName(); |
537 |
} |
538 |
|
539 |
@Override |
540 |
public Icon getIcon() { |
541 |
return null; |
542 |
} |
543 |
|
544 |
@Override |
545 |
public Project getProject() { |
546 |
return project; |
547 |
} |
548 |
|
549 |
@Override |
550 |
public void addPropertyChangeListener(PropertyChangeListener listener) { |
551 |
//Immutable |
552 |
} |
553 |
|
554 |
@Override |
555 |
public void removePropertyChangeListener(PropertyChangeListener listener) { |
556 |
////Immutable |
557 |
} |
558 |
} |
559 |
} |