001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.apisupport.project.ui.wizard.action;
043:
044: import java.util.Arrays;
045: import java.util.Collections;
046: import java.util.HashMap;
047: import java.util.HashSet;
048: import java.util.Map;
049: import java.util.Set;
050: import java.util.TreeSet;
051: import org.netbeans.modules.apisupport.project.CreatedModifiedFiles;
052: import org.netbeans.modules.apisupport.project.ui.wizard.BasicWizardIterator;
053: import org.openide.WizardDescriptor;
054: import org.openide.filesystems.FileObject;
055:
056: /**
057: * Data model used across the <em>New Action Wizard</em>.
058: */
059: final class DataModel extends BasicWizardIterator.BasicDataModel {
060:
061: static final String[] PREDEFINED_COOKIE_CLASSES;
062:
063: private static final String[] HARDCODED_IMPORTS = new String[] {
064: "org.openide.nodes.Node", // NOI18N
065: "org.openide.util.HelpCtx", // NOI18N
066: "org.openide.util.NbBundle", // NOI18N
067: "org.openide.util.actions.CookieAction" // NOI18N
068: };
069:
070: /** Maps FQCN to CNB. */
071: private static final Map<String, String> CLASS_TO_CNB;
072:
073: static {
074: Map<String, String> map = new HashMap<String, String>(5);
075: map
076: .put("org.openide.loaders.DataObject",
077: "org.openide.loaders"); // NOI18N
078: map.put("org.openide.cookies.EditCookie", "org.openide.nodes"); // NOI18N
079: map.put("org.openide.cookies.OpenCookie", "org.openide.nodes"); // NOI18N
080: map.put("org.netbeans.api.project.Project",
081: "org.netbeans.modules.projectapi"); // NOI18N
082: map.put("org.openide.cookies.EditorCookie", "org.openide.text"); // NOI18N
083: CLASS_TO_CNB = Collections.unmodifiableMap(map);
084: PREDEFINED_COOKIE_CLASSES = new String[5];
085: DataModel.CLASS_TO_CNB.keySet().toArray(
086: PREDEFINED_COOKIE_CLASSES);
087: }
088:
089: private static final String NEW_LINE = System
090: .getProperty("line.separator"); // NOI18N
091:
092: /** Default indent. (four spaces hardcoded currently). */
093: private static final String INDENT = " "; // NOI18N
094: /** Double {@link #INDENT}. */
095: private static final String INDENT_2X = INDENT + INDENT; // NOI18N
096:
097: private CreatedModifiedFiles cmf;
098:
099: // first panel data (Action Type)
100: private boolean alwaysEnabled;
101: private String[] cookieClasses;
102: private boolean multiSelection;
103:
104: // second panel data (GUI Registration)
105: private String category;
106:
107: // global menu item fields
108: private boolean globalMenuItemEnabled;
109: private String gmiParentMenuPath;
110: private Position gmiPosition;
111: private boolean gmiSeparatorAfter;
112: private boolean gmiSeparatorBefore;
113:
114: // global toolbar button fields
115: private boolean toolbarEnabled;
116: private String toolbar;
117: private Position toolbarPosition;
118:
119: // global keyboard shortcut
120: private boolean kbShortcutEnabled;
121: private final Set<String> keyStrokes = new HashSet<String>();
122:
123: // file type context menu item
124: private boolean ftContextEnabled;
125: private String ftContextType;
126: private Position ftContextPosition;
127: private boolean ftContextSeparatorAfter;
128: private boolean ftContextSeparatorBefore;
129:
130: // editor context menu item
131: private boolean edContextEnabled;
132: private String edContextType;
133: private Position edContextPosition;
134: private boolean edContextSeparatorAfter;
135: private boolean edContextSeparatorBefore;
136:
137: // third panel data (Name, Icon, and Location)
138: private String className;
139: private String displayName;
140: private String origIconPath;
141: private String largeIconPath;
142:
143: DataModel(WizardDescriptor wiz) {
144: super (wiz);
145: }
146:
147: private void regenerate() {
148: String dashedPkgName = getPackageName().replace('.', '-');
149: String dashedFqClassName = dashedPkgName + '-' + className;
150: String shadow = dashedFqClassName + ".shadow"; // NOI18N
151:
152: cmf = new CreatedModifiedFiles(getProject());
153:
154: String actionPath = getDefaultPackagePath(className + ".java",
155: false); // NOI18N
156: // XXX use nbresloc URL protocol rather than DataModel.class.getResource(...):
157: FileObject template = CreatedModifiedFiles
158: .getTemplate(alwaysEnabled ? "callableSystemAction.java"
159: : "cookieAction.java"); // NOI18N
160: assert template != null;
161: String actionNameKey = "CTL_" + className; // NOI18N
162: Map<String, String> replaceTokens = new HashMap<String, String>();
163: replaceTokens.put("CLASS_NAME", className); // NOI18N
164: replaceTokens.put("PACKAGE_NAME", getPackageName()); // NOI18N
165: replaceTokens.put("DISPLAY_NAME_KEY", actionNameKey); // NOI18N
166: replaceTokens.put("MODE", getSelectionMode()); // NOI18N
167: Set<String> imports = new TreeSet<String>(Arrays
168: .asList(HARDCODED_IMPORTS));
169: Set<String> addedFQNCs = new TreeSet<String>();
170: if (!alwaysEnabled) {
171: StringBuffer cookieSB = new StringBuffer();
172: for (String cookieClass : cookieClasses) {
173: // imports for predefined chosen cookie classes
174: if (CLASS_TO_CNB.containsKey(cookieClass)) {
175: addedFQNCs.add(cookieClass);
176: }
177: // cookie block
178: if (cookieSB.length() > 0) {
179: cookieSB.append(", ");
180: }
181: cookieSB.append(parseClassName(cookieClass) + ".class"); // NOI18N
182: }
183: replaceTokens.put("COOKIE_CLASSES_BLOCK", cookieSB
184: .toString()); // NOI18N
185: String impl;
186: if (cookieClasses.length == 1) {
187: String cName = parseClassName(cookieClasses[0]);
188: String cNameVar = Character
189: .toLowerCase(cName.charAt(0))
190: + cName.substring(1);
191: impl = cName + ' ' + cNameVar
192: + " = activatedNodes[0].getLookup().lookup("
193: + cName + ".class);\n" // NOI18N
194: + INDENT_2X + "// TODO use " + cNameVar; // NOI18N
195: } else {
196: impl = "// TODO implement action body"; // NOI18N
197: }
198: replaceTokens.put("PERFORM_ACTION_CODE", impl); // NOI18N
199: }
200: // imports
201: imports.addAll(addedFQNCs);
202: StringBuffer importsBuffer = new StringBuffer();
203: for (String imprt : imports) {
204: importsBuffer.append("import " + imprt + ';' + NEW_LINE); // NOI18N
205: }
206: replaceTokens.put("IMPORTS", importsBuffer.toString()); // NOI18N
207: cmf.add(cmf.createFileWithSubstitutions(actionPath, template,
208: replaceTokens));
209:
210: // Bundle.properties for localized action name
211: cmf
212: .add(cmf.bundleKey(getDefaultPackagePath(
213: "Bundle.properties", true), actionNameKey,
214: displayName)); // NOI18N
215:
216: // Copy action icon
217: if (origIconPath != null) {
218: String relativeIconPath = addCreateIconOperation(cmf,
219: origIconPath);
220: replaceTokens.put("ICON_RESOURCE_METHOD", DataModel
221: .generateIconResourceMethod(relativeIconPath)); // NOI18N
222: replaceTokens.put("INITIALIZE_METHOD", ""); // NOI18N
223: } else {
224: replaceTokens.put("ICON_RESOURCE_METHOD", ""); // NOI18N
225: replaceTokens.put("INITIALIZE_METHOD", DataModel
226: .generateNoIconInitializeMethod()); // NOI18N
227: }
228:
229: if (isToolbarEnabled() && largeIconPath != null) {
230: addCreateIconOperation(cmf, largeIconPath);
231: }
232:
233: // add layer entry about the action
234: String instanceFullPath = category + "/" // NOI18N
235: + dashedFqClassName + ".instance"; // NOI18N
236: cmf.add(cmf.createLayerEntry(instanceFullPath, null, null,
237: null, null));
238:
239: // add dependency on util to project.xml
240: cmf.add(cmf.addModuleDependency("org.openide.util")); // NOI18N
241: if (!alwaysEnabled) {
242: cmf.add(cmf.addModuleDependency("org.openide.nodes")); // NOI18N
243: for (String fqn : addedFQNCs) {
244: cmf.add(cmf.addModuleDependency(CLASS_TO_CNB.get(fqn)));
245: }
246: }
247:
248: // create layer entry for global menu item
249: if (globalMenuItemEnabled) {
250: generateShadowWithOrderAndSeparator(gmiParentMenuPath,
251: shadow, dashedPkgName, instanceFullPath,
252: gmiSeparatorBefore, gmiSeparatorAfter, gmiPosition);
253: }
254:
255: // create layer entry for toolbar button
256: if (toolbarEnabled) {
257: generateShadow(toolbar + "/" + shadow, instanceFullPath); // NOI18N
258: generateOrder(toolbar, toolbarPosition.getBefore(), shadow,
259: toolbarPosition.getAfter());
260: }
261:
262: // create layer entry for keyboard shortcut
263: if (kbShortcutEnabled) {
264: String parentPath = "Shortcuts"; // NOI18N
265: for (String keyStroke : keyStrokes) {
266: generateShadow(
267: parentPath + "/" + keyStroke + ".shadow",
268: instanceFullPath); // NOI18N
269: }
270: }
271:
272: // create file type context menu item
273: if (ftContextEnabled) {
274: generateShadowWithOrderAndSeparator(ftContextType, shadow,
275: dashedPkgName, instanceFullPath,
276: ftContextSeparatorBefore, ftContextSeparatorAfter,
277: ftContextPosition);
278: }
279:
280: // create editor context menu item
281: if (edContextEnabled) {
282: generateShadowWithOrderAndSeparator(edContextType, shadow,
283: dashedPkgName, instanceFullPath,
284: edContextSeparatorBefore, edContextSeparatorAfter,
285: edContextPosition);
286: }
287: }
288:
289: private void generateShadowWithOrderAndSeparator(
290: final String parentPath, final String shadow,
291: final String dashedPkgName, final String instanceFullPath,
292: final boolean separatorBefore,
293: final boolean separatorAfter, final Position position) {
294: generateShadow(parentPath + "/" + shadow, instanceFullPath); // NOI18N
295: generateOrder(parentPath, position.getBefore(), shadow,
296: position.getAfter());
297: if (separatorBefore) {
298: String sepName = dashedPkgName
299: + "-separatorBefore.instance"; // NOI18N
300: generateSeparator(parentPath, sepName);
301: generateOrder(parentPath, position.getBefore(), sepName,
302: shadow);
303: }
304: if (separatorAfter) {
305: String sepName = dashedPkgName + "-separatorAfter.instance"; // NOI18N
306: generateSeparator(parentPath, sepName);
307: generateOrder(parentPath, shadow, sepName, position
308: .getAfter());
309: }
310: }
311:
312: /**
313: * Just a helper convenient method for cleaner code.
314: */
315: private void generateOrder(String layerPath, String before,
316: String nue, String after) {
317: cmf.add(cmf.orderLayerEntry(layerPath, before, nue, after));
318: }
319:
320: /** Checks whether a proposed class exists. */
321: boolean classExists() {
322: FileObject classFO = getProject().getProjectDirectory()
323: .getFileObject(
324: getDefaultPackagePath(className + ".java",
325: false)); // NOI18N
326: return classFO != null;
327: }
328:
329: private void generateShadow(final String itemPath,
330: final String origInstance) {
331: cmf.add(cmf.createLayerEntry(itemPath, null, null, null, null));
332: cmf.add(cmf.createLayerAttribute(itemPath, "originalFile",
333: origInstance)); // NOI18N
334: }
335:
336: CreatedModifiedFiles getCreatedModifiedFiles() {
337: if (cmf == null) {
338: regenerate();
339: }
340: return cmf;
341: }
342:
343: private void reset() {
344: cmf = null;
345: }
346:
347: void setAlwaysEnabled(boolean alwaysEnabled) {
348: this .alwaysEnabled = alwaysEnabled;
349: }
350:
351: boolean isAlwaysEnabled() {
352: return alwaysEnabled;
353: }
354:
355: void setCookieClasses(String[] cookieClasses) {
356: this .cookieClasses = cookieClasses;
357: }
358:
359: void setMultiSelection(boolean multiSelection) {
360: this .multiSelection = multiSelection;
361: }
362:
363: private String getSelectionMode() {
364: return multiSelection ? "MODE_ALL" : "MODE_EXACTLY_ONE"; // NOI18N
365: }
366:
367: void setCategory(String category) {
368: this .category = category;
369: }
370:
371: void setClassName(String className) {
372: reset();
373: this .className = className;
374: }
375:
376: public String getClassName() {
377: return className;
378: }
379:
380: void setDisplayName(String display) {
381: this .displayName = display;
382: }
383:
384: public String getDisplayName() {
385: return displayName;
386: }
387:
388: void setIconPath(String origIconPath) {
389: reset();
390: this .origIconPath = origIconPath;
391: }
392:
393: public String getIconPath() {
394: return origIconPath;
395: }
396:
397: @Override
398: public void setPackageName(String pkg) {
399: super .setPackageName(pkg);
400: reset();
401: }
402:
403: void setGlobalMenuItemEnabled(boolean globalMenuItemEnabled) {
404: this .globalMenuItemEnabled = globalMenuItemEnabled;
405: }
406:
407: void setGMIParentMenu(String gmiParentMenuPath) {
408: this .gmiParentMenuPath = gmiParentMenuPath;
409: }
410:
411: void setGMISeparatorAfter(boolean gmiSeparatorAfter) {
412: this .gmiSeparatorAfter = gmiSeparatorAfter;
413: }
414:
415: void setGMISeparatorBefore(boolean gmiSeparatorBefore) {
416: this .gmiSeparatorBefore = gmiSeparatorBefore;
417: }
418:
419: void setGMIPosition(Position position) {
420: this .gmiPosition = position;
421: }
422:
423: void setToolbarEnabled(boolean toolbarEnabled) {
424: this .toolbarEnabled = toolbarEnabled;
425: }
426:
427: boolean isToolbarEnabled() {
428: return toolbarEnabled;
429: }
430:
431: void setToolbar(String toolbar) {
432: this .toolbar = toolbar;
433: }
434:
435: void setToolbarPosition(Position position) {
436: this .toolbarPosition = position;
437: }
438:
439: void setKeyboardShortcutEnabled(boolean kbShortcutEnabled) {
440: this .kbShortcutEnabled = kbShortcutEnabled;
441: }
442:
443: void setKeyStroke(String keyStroke) {
444: keyStrokes.add(keyStroke);
445: }
446:
447: void setFileTypeContextEnabled(boolean contextEnabled) {
448: this .ftContextEnabled = contextEnabled;
449: }
450:
451: void setFTContextType(String contextType) {
452: this .ftContextType = contextType;
453: }
454:
455: void setFTContextPosition(Position position) {
456: this .ftContextPosition = position;
457: }
458:
459: void setFTContextSeparatorAfter(boolean separator) {
460: this .ftContextSeparatorAfter = separator;
461: }
462:
463: void setFTContextSeparatorBefore(boolean separator) {
464: this .ftContextSeparatorBefore = separator;
465: }
466:
467: void setEditorContextEnabled(boolean contextEnabled) {
468: this .edContextEnabled = contextEnabled;
469: }
470:
471: void setEdContextType(String contextType) {
472: this .edContextType = contextType;
473: }
474:
475: void setEdContextPosition(Position position) {
476: this .edContextPosition = position;
477: }
478:
479: void setEdContextSeparatorAfter(boolean separator) {
480: this .edContextSeparatorAfter = separator;
481: }
482:
483: void setEdContextSeparatorBefore(boolean separator) {
484: this .edContextSeparatorBefore = separator;
485: }
486:
487: static final class Position {
488:
489: private String before;
490: private String after;
491: private String beforeName;
492: private String afterName;
493:
494: Position(String before, String after) {
495: this (before, after, null, null);
496: }
497:
498: Position(String before, String after, String beforeName,
499: String afterName) {
500: this .before = before;
501: this .after = after;
502: this .beforeName = beforeName;
503: this .afterName = afterName;
504: }
505:
506: String getBefore() {
507: return before;
508: }
509:
510: String getAfter() {
511: return after;
512: }
513:
514: String getBeforeName() {
515: return beforeName;
516: }
517:
518: String getAfterName() {
519: return afterName;
520: }
521: }
522:
523: private void generateSeparator(final String parentPath,
524: final String sepName) {
525: String sepPath = parentPath + "/" + sepName; // NOI18N
526: cmf.add(cmf.createLayerEntry(sepPath, null, null, null, null));
527: cmf.add(cmf.createLayerAttribute(sepPath, "instanceClass", // NOI18N
528: "javax.swing.JSeparator")); // NOI18N
529: }
530:
531: /**
532: * Parse class name from a fully qualified class name. If the given name
533: * doesn't contain dot (<em>.</em>), given parameter is returned.
534: */
535: static String parseClassName(final String name) {
536: int lastDot = name.lastIndexOf('.');
537: return lastDot == -1 ? name : name.substring(lastDot + 1);
538: }
539:
540: private static String generateIconResourceMethod(
541: final String relativeIconPath) {
542: return NEW_LINE + INDENT + "@Override"
543: + NEW_LINE
544: + // NOI18N
545: INDENT + "protected String iconResource() {"
546: + NEW_LINE
547: + // NOI18N
548: INDENT_2X + "return \"" + relativeIconPath + "\";"
549: + NEW_LINE + // NOI18N
550: INDENT + "}"; // NOI18N
551: }
552:
553: private static String generateNoIconInitializeMethod() {
554: return "@Override"
555: + NEW_LINE
556: + // NOI18N
557: INDENT
558: + "protected void initialize() {"
559: + NEW_LINE
560: + // NOI18N
561: INDENT_2X
562: + "super.initialize();"
563: + NEW_LINE
564: + // NOI18N
565: INDENT_2X
566: + "// see org.openide.util.actions.SystemAction.iconResource() Javadoc for more details"
567: + NEW_LINE
568: + // NOI18N
569: INDENT_2X + "putValue(\"noIconInMenu\", Boolean.TRUE);"
570: + NEW_LINE + // NOI18N
571: INDENT + "}" + NEW_LINE; // NOI18N
572: }
573:
574: public void setLargeIconPath(String largeIconPath) {
575: this.largeIconPath = largeIconPath;
576: }
577:
578: }
|