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.actions.simple;
043:
044: import java.awt.Toolkit;
045: import java.awt.event.KeyEvent;
046: import java.io.File;
047: import java.net.URL;
048: import java.util.ArrayList;
049: import java.util.Arrays;
050: import java.util.HashMap;
051: import java.util.HashSet;
052: import java.util.Iterator;
053: import java.util.List;
054: import java.util.Map;
055: import java.util.Set;
056: import java.util.Stack;
057: import java.util.StringTokenizer;
058: import javax.swing.Icon;
059: import javax.swing.ImageIcon;
060: import javax.swing.KeyStroke;
061: import org.netbeans.actions.spi.ActionProvider;
062: import org.netbeans.actions.spi.ContainerProvider;
063: import org.openide.xml.XMLUtil;
064: import org.xml.sax.*;
065: import org.xml.sax.helpers.XMLReaderAdapter;
066:
067: /** A quick n dirty, really ugly class that parses the XML file
068: * and builds a bunch
069: * of hashmaps with the results.
070: */
071:
072: public class Interpreter implements org.xml.sax.DocumentHandler {
073: private URL url;
074: private boolean parsed = false;
075:
076: /** Create a new instance of NBTheme */
077: public Interpreter(URL url) {
078: this .url = url;
079: }
080:
081: private void ensureParsed() {
082: if (!parsed) {
083: parse();
084: parsed = true;
085: }
086: }
087:
088: private void parse() {
089: try {
090: Parser p = new XMLReaderAdapter(XMLUtil.createXMLReader());
091: p.setDocumentHandler(this );
092: String externalForm = url.toExternalForm();
093: InputSource is = new InputSource(externalForm);
094: try {
095: p.parse(is);
096: } catch (NullPointerException npe) {
097: npe.printStackTrace();
098: if (npe.getCause() != null) {
099: npe.getCause().printStackTrace();
100: }
101: }
102: } catch (java.io.IOException ie) {
103: ie.printStackTrace();
104: } catch (org.xml.sax.SAXException se) {
105: se.printStackTrace();
106: }
107: }
108:
109: public void endDocument() throws org.xml.sax.SAXException {
110: }
111:
112: public void startDocument() throws org.xml.sax.SAXException {
113: }
114:
115: private static final String ACTIONSET = "actionset";
116: private static final String CONTAINER = "container";
117: private static final String ACTION = "action";
118: private static final String CONSTRAINT = "constraint";
119: private static final String KEY = "key";
120: private boolean inActionSet = false;
121: private boolean inContainer = false;
122: private String currContainer = null;
123: private String currAction = null;
124: private boolean inAction = false;
125: private boolean inConstraint = false;
126: private boolean inInvoker = false;
127: private String currInvoker = null;
128: private String currConstraint = null;
129: private static final String INVOKER = "invoker";
130: private boolean inItem = false;
131: private static final String ITEM = "item";
132: boolean inKey = false;
133: private String currItem = null;
134:
135: private boolean inKeystroke = false;
136: private Map keystrokesToActions = new HashMap();
137:
138: private Map containersToItems = new HashMap();
139: private Map constraintsToKeys = new HashMap();
140: private Map actionsToConstraints = new HashMap();
141: private Set toolbarContainers = new HashSet();
142: private Set menuContainers = new HashSet();
143: private Map constraints = new HashMap();
144: private Map invokers = new HashMap();
145:
146: public String[] getContainerNames() {
147: ensureParsed();
148: String[] result = new String[containersToItems.size()];
149: result = (String[]) containersToItems.keySet().toArray(result);
150: return result;
151: }
152:
153: public String[] getActionNames(String containerCtx) {
154: ensureParsed();
155: List l = (List) containersToItems.get(containerCtx);
156: if (l == null) {
157: throw new IllegalArgumentException(
158: "Not a known container ctx: " + containerCtx);
159: }
160: String[] result = new String[l.size()];
161: result = (String[]) l.toArray(result);
162: return result;
163: }
164:
165: public boolean contextContainsAction(String containerCtx,
166: String action) {
167: return Arrays.asList(getActionNames(containerCtx)).contains(
168: action);
169: }
170:
171: public int getState(String action, Map context) {
172: ensureParsed();
173: List l = (List) actionsToConstraints.get(action);
174: if (l == null) {
175: return ActionProvider.STATE_ENABLED
176: | ActionProvider.STATE_VISIBLE;
177: }
178: Iterator i = l.iterator();
179:
180: boolean enabled = true;
181: boolean visible = true;
182:
183: while (i.hasNext()) {
184: Object o = null;
185: SimpleConstraint c = null;
186: try {
187: o = i.next();
188: c = (SimpleConstraint) constraints.get(o);
189: } catch (ClassCastException cce) {
190: throw new ClassCastException(
191: "Looking for SimpleConstraint but got " + o);
192: }
193: if (c.isEnabledType()) {
194: enabled &= c.test(context);
195: } else {
196: visible &= c.test(context);
197: }
198: if (!enabled && !visible) {
199: break;
200: }
201: }
202: int result = 0;
203: if (enabled)
204: result |= ActionProvider.STATE_ENABLED;
205: if (visible)
206: result |= ActionProvider.STATE_VISIBLE;
207: return result;
208: }
209:
210: private void addItemToContainer(String container, String item) {
211: List l = (List) containersToItems.get(container);
212: if (l == null) {
213: l = new ArrayList();
214: containersToItems.put(container, l);
215: }
216: l.add(item);
217: }
218:
219: private void addKeyToConstraint(String constraint, SimpleKey key) {
220: List l = (List) constraintsToKeys.get(constraint);
221: if (l == null) {
222: l = new ArrayList();
223: constraintsToKeys.put(constraint, l);
224: }
225: l.add(key);
226: }
227:
228: public SimpleInvoker getInvoker(String action) {
229: String invoker = (String) actionsToInvokers.get(action);
230: SimpleInvoker result = (SimpleInvoker) invokers.get(invoker);
231: return result;
232: }
233:
234: private HashMap actionsToInvokers = new HashMap();
235:
236: private void addInvokerToAction(String action, String invoker)
237: throws SAXException {
238: String inv = (String) actionsToInvokers.get(action);
239: if (inv == null) {
240: actionsToInvokers.put(action, invoker);
241: } else {
242: throw new SAXException("Attempt to redefine invoker " + inv
243: + " on " + action + " with " + invoker);
244: }
245: }
246:
247: private void addConstraintsToAction(String action,
248: String constraints) {
249: String[] cns;
250: if (constraints.indexOf(',') != -1) {
251: StringTokenizer tk = new StringTokenizer(constraints, ",");
252: cns = new String[tk.countTokens()];
253: int i = 0;
254: while (tk.hasMoreTokens()) {
255: cns[i] = tk.nextToken();
256: i++;
257: }
258: } else {
259: cns = new String[] { constraints };
260: }
261: List l = (List) actionsToConstraints.get(action);
262: if (l == null) {
263: l = new ArrayList();
264: actionsToConstraints.put(action, l);
265: }
266: l.addAll(Arrays.asList(cns));
267: }
268:
269: private void addKeyStrokeToAction(String action, AttributeList l)
270: throws SAXException {
271: KeyStroke ks = attsToKeystroke(l);
272: keystrokesToActions.put(ks, action);
273: actionsToKeystrokes.put(action, ks);
274: }
275:
276: private void addIconToAction(String action, String imgpath) {
277: imagesToIcons.put(action, imgpath);
278: }
279:
280: public Icon getIconForAction(String action) {
281: String partialPath = (String) imagesToIcons.get(action);
282: if (partialPath != null) {
283: String s = url.toExternalForm();
284: int idx = s.lastIndexOf("/");
285: String urlString = s.substring(0, idx) + "/" + partialPath;
286: try {
287: URL url = new URL(urlString);
288: return new ImageIcon(Toolkit.getDefaultToolkit()
289: .getImage(url));
290: } catch (Exception e) {
291: e.printStackTrace();
292: }
293: }
294: return null;
295: }
296:
297: private HashMap imagesToIcons = new HashMap();
298:
299: public KeyStroke[] getAllKeystrokes() {
300: KeyStroke[] result = new KeyStroke[keystrokesToActions.size()];
301: result = (KeyStroke[]) keystrokesToActions.keySet().toArray(
302: result);
303: return result;
304: }
305:
306: public String[] getAllActionsBoundToKeystrokes() {
307: String[] result = new String[actionsToKeystrokes.size()];
308: result = (String[]) actionsToKeystrokes.keySet()
309: .toArray(result);
310: return result;
311: }
312:
313: public KeyStroke getKeyStrokeForAction(String action) {
314: return (KeyStroke) actionsToKeystrokes.get(action);
315: }
316:
317: public String getActionForKeystroke(KeyStroke stroke) {
318: return (String) keystrokesToActions.get(stroke);
319: }
320:
321: private Map actionsToKeystrokes = new HashMap();
322:
323: String KATT_MODIFIERS = "modifiers";
324: String KATT_KEY = "key";
325: String KATT_TYPEDKEY = "typedkey";
326: String KVAL_DEFAULTMOD = "defaultAccelerator";
327:
328: private KeyStroke attsToKeystroke(AttributeList l)
329: throws SAXException {
330: String modifiers = l.getValue(KATT_MODIFIERS);
331: String key = l.getValue(KATT_KEY);
332: String typedKey = l.getValue(KATT_TYPEDKEY);
333:
334: StringBuffer sb = new StringBuffer();
335: if (typedKey != null) {
336: sb.append("typed ");
337: sb.append(typedKey);
338: } else {
339: if (modifiers != null) {
340: int dmod = modifiers.indexOf(KVAL_DEFAULTMOD);
341: if (dmod != -1) {
342: StringBuffer rep = new StringBuffer(modifiers);
343: rep.replace(dmod, KVAL_DEFAULTMOD.length(),
344: getDefaultModifiersString());
345: modifiers = rep.toString();
346: }
347:
348: sb.append(modifiers);
349: sb.append(' ');
350: }
351: sb.append(key);
352: }
353:
354: KeyStroke result = KeyStroke.getKeyStroke(sb.toString());
355: if (result == null) {
356: throw new SAXException("Misdefined keystroke:"
357: + sb.toString());
358: }
359: return result;
360: }
361:
362: private String getDefaultModifiersString() {
363: if (defaultModsText == null) {
364: int mask = Toolkit.getDefaultToolkit()
365: .getMenuShortcutKeyMask();
366: String toConvert = KeyEvent.getKeyModifiersText(mask)
367: .toLowerCase();
368: char[] chars = toConvert.toCharArray();
369: for (int i = 0; i < chars.length; i++) {
370: if (chars[i] == '+') {
371: chars[i] = ' ';
372: }
373: }
374: defaultModsText = new String(chars);
375: int cidx = toConvert.indexOf("command");
376: //Replace apple's meaningless "command" with meta
377: if (cidx != -1) {
378: StringBuffer sb = new StringBuffer(defaultModsText);
379: sb.replace(cidx, cidx + 7, "meta");
380: defaultModsText = sb.toString();
381: }
382:
383: }
384: return defaultModsText;
385: }
386:
387: private String defaultModsText = null;
388:
389: private static final String MENU_TYPE = "menu";
390: private static final String TOOLBAR_TYPE = "toolbar";
391: private static final String KEYSTROKE = "keystroke";
392:
393: private String currMode = null;
394: private String currType = null;
395:
396: String[] menus = null;
397:
398: public String[] getMenus() {
399: ensureParsed();
400: if (menus == null) {
401: ArrayList al = new ArrayList();
402: Iterator i = menuContainers.iterator();
403: while (i.hasNext()) {
404: String s = (String) i.next();
405: if (!ContainerProvider.CONTEXTMENU_CONTEXT.equals(s)) {
406: al.add(s);
407: }
408: }
409: menus = new String[al.size()];
410: menus = (String[]) al.toArray(menus);
411: }
412: return menus;
413: }
414:
415: public String[] getToolbars() {
416: ensureParsed();
417: String[] result = new String[toolbarContainers.size()];
418: result = (String[]) toolbarContainers.toArray(result);
419: return result;
420: }
421:
422: private static final String CONTEXTMENU_TYPE = "contextmenu";
423:
424: public void startElement(String tag, AttributeList atts)
425: throws SAXException {
426:
427: if (ACTIONSET.equals(tag)) {
428: inActionSet = true;
429: }
430:
431: if (CONTAINER.equals(tag)) {
432: inContainer = true;
433: currContainer = findName(atts);
434: String type = findType(atts);
435: if (MENU_TYPE.equals(type)) {
436: menuContainers.add(currContainer);
437: } else if (TOOLBAR_TYPE.equals(type)) {
438: toolbarContainers.add(currContainer);
439: } else if (CONTEXTMENU_TYPE.equals(type)) {
440: menuContainers.add(currContainer);
441: } else {
442: throw new SAXException("Unknown container type: "
443: + type);
444: }
445: }
446:
447: if (ITEM.equals(tag)) {
448: inItem = true;
449: if (!inContainer) {
450: throw new SAXException(
451: "Item declared outside of container context "
452: + atts);
453: }
454: currItem = findName(atts);
455: addItemToContainer(currContainer, currItem);
456: }
457:
458: if (ACTION.equals(tag)) {
459: inAction = true;
460: if (inContainer || inKey || inInvoker || inConstraint) {
461: throw new SAXException("Cannot create an action here");
462: }
463: currAction = findName(atts);
464: String constraints = findConstraints(atts);
465: if (constraints != null) {
466: addConstraintsToAction(currAction, constraints);
467: }
468:
469: String icon = findIcon(atts);
470: if (icon != null) {
471: addIconToAction(currAction, icon);
472: }
473: String invoker = findInvoker(atts);
474: addInvokerToAction(currAction, invoker);
475: }
476:
477: if (CONSTRAINT.equals(tag)) {
478: inConstraint = true;
479: currKeys = new HashMap();
480: currConstraint = findName(atts);
481: currType = findType(atts);
482: }
483:
484: if (KEY.equals(tag)) {
485: if (!inConstraint) {
486: throw new SAXException(
487: "Keys can only be defined inside constraints");
488: }
489: inKey = true;
490: currMode = findMode(atts);
491: currMethod = findKeyMethod(atts);
492: currClass = findKeyClass(atts);
493: currValue = findKeyValue(atts);
494: currMatch = findMatch(atts);
495: currMustContain = currMode == null ? true : "mustcontain"
496: .equals(currMode);
497: if (currValue == null) {
498: throw new SAXException(
499: "Key must always have a value attribute");
500: }
501: //addKeyToConstraint (currConstraint, value);
502: }
503:
504: if (KEYSTROKE.equals(tag)) {
505: inKeystroke = true;
506: String act = atts.getValue("action");
507: if (act == null) {
508: throw new IllegalArgumentException(
509: "keystroke must map to an action");
510: }
511: addKeyStrokeToAction(act, atts);
512: }
513:
514: if (INVOKER.equals(tag)) {
515: inInvoker = true;
516: currInvoker = findName(atts);
517: String type = findType(atts);
518: lastInvokerMethod = findMethod(atts);
519: lastInvokerClass = findClass(atts);
520: lastInvokerWasDirect = ITYPE_DIRECT.equals(type);
521: }
522: }
523:
524: private boolean currMustContain = false;
525: private String currMatch = null;
526: private String currMethod = null;
527: private String currClass = null;
528: private String currValue = null;
529: private Map currKeys = null;
530: private static final String ITYPE_DIRECT = "direct";
531: private String lastInvokerMethod = null;
532: private String lastInvokerClass = null;
533: private boolean lastInvokerWasDirect = true;
534:
535: public void endElement(java.lang.String tag)
536: throws org.xml.sax.SAXException {
537:
538: if (ACTIONSET.equals(tag)) {
539: inActionSet = false;
540: }
541:
542: if (CONTAINER.equals(tag)) {
543: inContainer = false;
544: currContainer = null;
545: }
546:
547: if (ITEM.equals(tag)) {
548: inItem = false;
549: currItem = null;
550: }
551:
552: if (ACTION.equals(tag)) {
553: inAction = false;
554: currAction = null;
555: }
556:
557: if (CONSTRAINT.equals(tag)) {
558: boolean enabledType = currType == null
559: || CTYPE_ENABLED.equals(currType);
560:
561: List l = (List) constraintsToKeys.get(currConstraint);
562: if (l == null) {
563: throw new SAXException("Constraint " + currConstraint
564: + " defines no keys");
565: }
566: SimpleKey[] keys = new SimpleKey[l.size()];
567: keys = (SimpleKey[]) l.toArray(keys);
568: ArrayList includes = new ArrayList(keys.length);
569: ArrayList excludes = new ArrayList(keys.length);
570: for (int i = 0; i < keys.length; i++) {
571: if (keys[i].isMustContain()) {
572: includes.add(keys[i]);
573: } else {
574: excludes.add(keys[i]);
575: }
576: }
577: SimpleKey[] incs = new SimpleKey[includes.size()];
578: SimpleKey[] excs = new SimpleKey[excludes.size()];
579: incs = (SimpleKey[]) includes.toArray(incs);
580: excs = (SimpleKey[]) excludes.toArray(excs);
581:
582: constraints.put(currConstraint, new SimpleConstraint(
583: currConstraint, incs, excs, enabledType));
584:
585: inConstraint = false;
586: currConstraint = null;
587: currKeys = null;
588: currType = null;
589: currMatch = null;
590: }
591:
592: if (KEY.equals(tag)) {
593: boolean exclude = MODE_EXCLUSIVE.equals(currMode);
594: /*
595: currMode = findMode(atts);
596: currType = findType(atts);
597: currMethod = findKeyMethod(atts);
598: currClass = findKeyClass(atts);
599: currValue = findKeyValue(atts);
600: */
601: SimpleKey key = new SimpleKey(currValue, currMethod,
602: currClass, currMustContain, currMatch);
603: addKeyToConstraint(currConstraint, key);
604: currMode = null;
605: currType = null;
606: currMethod = null;
607: currClass = null;
608: currValue = null;
609: inKey = false;
610: }
611:
612: if (INVOKER.equals(tag)) {
613: SimpleInvoker inv = new SimpleInvoker(currInvoker,
614: lastInvokerClass, lastInvokerMethod,
615: lastInvokerWasDirect);
616: invokers.put(inv.getName(), inv);
617: inInvoker = false;
618: currInvoker = null;
619: }
620: }
621:
622: private static final String MODE_EXCLUSIVE = "mustnotcontain";
623: private static final String CTYPE_ENABLED = "enabled";
624:
625: private static final String ATT_NAME = "name";
626: private static final String ATT_TYPE = "type";
627: private static final String ATT_CONSTRAINTS = "constraints";
628:
629: private String findName(AttributeList l) throws SAXException {
630: String result = l.getValue(ATT_NAME);
631: if (result != null) {
632: return result.intern();
633: } else {
634: throw new SAXException("No name supplied");
635: }
636: }
637:
638: private static final String ATT_ICON = "icon";
639:
640: private String findIcon(AttributeList l) throws SAXException {
641: String result = l.getValue(ATT_ICON);
642: if (result != null) {
643: return result.intern();
644: }
645: return null;
646: }
647:
648: private static final String ATT_METHOD = "method";
649: private static final String ATT_CLASS = "class";
650:
651: private String findMethod(AttributeList l) throws SAXException {
652: String result = l.getValue(ATT_METHOD);
653: if (result != null) {
654: return result.intern();
655: } else {
656: throw new SAXException("No method supplied");
657: }
658: }
659:
660: private static final String ATT_MATCH = "match";
661:
662: private String findMatch(AttributeList l) throws SAXException {
663: String result = l.getValue(ATT_MATCH);
664: if (result != null) {
665: return result.intern();
666: }
667: return null;
668: }
669:
670: private String findKeyMethod(AttributeList l) throws SAXException {
671: String result = l.getValue(ATT_METHOD);
672: if (result != null) {
673: return result.intern();
674: }
675: return null;
676: }
677:
678: private String findKeyClass(AttributeList l) throws SAXException {
679: String result = l.getValue(ATT_CLASS);
680: if (result != null) {
681: return result.intern();
682: }
683: return null;
684: }
685:
686: private String findClass(AttributeList l) throws SAXException {
687: String result = l.getValue(ATT_CLASS);
688: if (result != null) {
689: return result.intern();
690: } else {
691: throw new SAXException("No class supplied");
692: }
693: }
694:
695: private String findType(AttributeList l) throws SAXException {
696: String result = l.getValue(ATT_TYPE);
697: if (result != null) {
698: return result.intern();
699: } else {
700: return null;
701: }
702: }
703:
704: private String findConstraints(AttributeList l) throws SAXException {
705: String result = l.getValue(ATT_CONSTRAINTS);
706: if (result != null) {
707: return result.intern();
708: } else {
709: return null;
710: }
711: }
712:
713: private static final String ATT_VALUE = "value";;
714:
715: private String findKeyValue(AttributeList l) throws SAXException {
716: String result = l.getValue(ATT_VALUE);
717: if (result != null) {
718: return result.intern();
719: } else {
720: throw new SAXException("Key must have a value");
721: }
722: }
723:
724: private static final String ATT_INVOKER = "invoker";
725:
726: private String findInvoker(AttributeList l) throws SAXException {
727: String result = l.getValue(ATT_INVOKER);
728: if (result != null) {
729: return result.intern();
730: } else {
731: throw new SAXException("Action must have an invoker");
732: }
733: }
734:
735: private static final String ATT_MODE = "mode";
736:
737: private String findMode(AttributeList l) throws SAXException {
738: String result = l.getValue(ATT_MODE);
739: if (result != null) {
740: return result.intern();
741: }
742: return null;
743: }
744:
745: public void characters(char[] p1, int p2, int p3)
746: throws org.xml.sax.SAXException {
747: }
748:
749: public void setDocumentLocator(org.xml.sax.Locator p1) {
750: }
751:
752: public void ignorableWhitespace(char[] p1, int p2, int p3)
753: throws org.xml.sax.SAXException {
754: }
755:
756: public void processingInstruction(java.lang.String p1,
757: java.lang.String p2) throws org.xml.sax.SAXException {
758: }
759:
760: }
|