Commits

Anonymous committed d655b38

Parse modern npm output; support ints, booleans and arrays of both in json parser (why oh why am I maintaining this?); support looking for libraries in all the places npm can hide them; parse javascript sources for require calls

Comments (0)

Files changed (12)

nodejs/manifest.mf

 OpenIDE-Module-Layer: org/netbeans/modules/nodejs/layer.xml
 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/nodejs/Bundle.properties
 OpenIDE-Module-Requires: org.openide.modules.os.Unix
-OpenIDE-Module-Specification-Version: 1.15
+OpenIDE-Module-Specification-Version: 1.16
 

nodejs/src/org/netbeans/modules/nodejs/DefaultExectable.java

             Process p = b.start();
             try {
                 InputStream in = p.getInputStream();
-                p.waitFor();
-                if (p.exitValue() == 0) {
-                    ByteArrayOutputStream out = new ByteArrayOutputStream();
-                    FileUtil.copy(in, out);
-                    String result = new String(out.toByteArray()).trim(); //trim off \n
-                    return result.length() == 0 ? null : result;
+                try {
+                    p.waitFor();
+                    if (p.exitValue() == 0) {
+                        ByteArrayOutputStream out = new ByteArrayOutputStream();
+                        FileUtil.copy(in, out);
+                        String result = new String(out.toByteArray()).trim(); //trim off \n
+                        return result.length() == 0 ? null : result;
+                    }
+                } finally {
+                    in.close();
                 }
             } catch (InterruptedException ex) {
                 Exceptions.printStackTrace(ex);

nodejs/src/org/netbeans/modules/nodejs/NodeJSProject.java

     private final NodeJsClassPathProvider classpath = new NodeJsClassPathProvider();
     private final PropertyChangeSupport supp = new PropertyChangeSupport(this);
     private final Sources sources = new NodeJSProjectSources(this);
-    private final Lookup lookup = Lookups.fixed(this, new NodeJSProjectProperties(this), classpath, sources);
+    private final Lookup lookup = Lookups.fixed(this, new NodeJSProjectProperties(this), classpath, sources, new NodeJsEncodingQuery());
 
     @SuppressWarnings("LeakingThisInConstructor")
     NodeJSProject(FileObject dir, ProjectState state) {

nodejs/src/org/netbeans/modules/nodejs/NodeJSProjectProperties.java

                 fo = project.getProjectDirectory().createData(".nbrun");
             }
             OutputStream out = fo.getOutputStream();
-            PrintStream ps = new PrintStream(out);
-            ps.println(args);
+            try {
+                PrintStream ps = new PrintStream(out);
+                try {
+                    ps.println(args);
+                } finally {
+                    ps.close();
+                }
+            } finally {
+                out.close();
+            }
         } catch (IOException ex) {
             Exceptions.printStackTrace(ex);
         }

nodejs/src/org/netbeans/modules/nodejs/NodeJsEncodingQuery.java

+package org.netbeans.modules.nodejs;
+
+import java.nio.charset.Charset;
+import org.netbeans.spi.queries.FileEncodingQueryImplementation;
+import org.openide.filesystems.FileObject;
+
+public class NodeJsEncodingQuery extends FileEncodingQueryImplementation {
+
+    @Override
+    public Charset getEncoding(FileObject file) {
+        return Charset.forName("UTF-8");
+    }
+}

nodejs/src/org/netbeans/modules/nodejs/NodeProjectSourceNodeFactory.java

 import java.awt.EventQueue;
 import java.awt.Image;
 import java.awt.event.ActionEvent;
+import java.io.File;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import javax.swing.AbstractAction;
 import javax.swing.Action;
 import javax.swing.BorderFactory;
 import org.openide.filesystems.FileUtil;
 import org.openide.loaders.DataObject;
 import org.openide.loaders.DataObjectNotFoundException;
+import org.openide.nodes.AbstractNode;
 import org.openide.nodes.ChildFactory;
+import org.openide.nodes.Children;
 import org.openide.nodes.FilterNode;
 import org.openide.nodes.Node;
 import org.openide.util.ChangeSupport;
 import org.openide.util.Exceptions;
 import org.openide.util.ImageUtilities;
 import org.openide.util.NbBundle;
+import org.openide.util.NbCollections;
 import org.openide.util.RequestProcessor;
 import org.openide.windows.TopComponent;
 
     private final ChangeSupport supp = new ChangeSupport(this);
     private final Project project;
 
+    private static final String[] builtInNodeLibs = new String[]{"assert", "buffer", "buffer_ieee754", "child_process", "cluster", "console", "constants", "crypto", "dgram", "dns", "events", "freelist", "fs", "http", "https", "module", "net", "os", "path", "punycode", "querystring", "readline", "repl", "stream", "string_decoder", "sys", "timers", "tls", "tty", "url", "util", "vm", "zlib"};
+    public static final Pattern CHECK_FOR_REQUIRE = Pattern.compile("require\\s??\\(\\s??['\"](.*?)['\"]\\s??\\)", 40);
+
     public NodeProjectSourceNodeFactory(Project p) {
         this.project = p;
     }
         return new NodeProjectSourceNodeFactory(p);
     }
 
-    @Override
     public List<Key> keys() {
         List<Key> keys = new ArrayList<Key>();
-        
+
         FileObject libFolder = project.getProjectDirectory().getFileObject("node_modules");
         VisibilityQuery q = VisibilityQuery.getDefault();
-        for (FileObject fo : project.getProjectDirectory().getChildren()) {
+        FileObject[] files = this.project.getProjectDirectory().getChildren();
+        Arrays.sort(files, new FOComparator());
+        List<FileObject> flds = new LinkedList();
+        for (FileObject fo : files) {
+            if (fo.equals(libFolder)) {
+                continue;
+            }
             if (q.isVisible(fo)) {
                 if (fo.isData()) {
                     keys.add(new Key(KeyTypes.SOURCE, fo));
                     if (fo.getName().equals("package.json") || fo.equals(libFolder)) {
                         continue;
                     }
-                    keys.add(new Key(KeyTypes.SOURCE, fo));
+                    flds.add(fo);
                 }
             }
         }
-        //now add libraries
+        Map<String, List<FileObject>> otherLibs = findOtherModules(this.project.getProjectDirectory());
+
         if (libFolder != null) {
-            List<Key> libFolders = new ArrayList<Key>();
+            List libFolders = new ArrayList();
             for (FileObject lib : libFolder.getChildren()) {
                 boolean visible = q.isVisible(lib);
-                if (visible && !"node_modules".equals(lib.getName()) && !"nbproject".equals(lib.getName()) && lib.isFolder()) {
+                if ((visible) && (!"node_modules".equals(lib.getName())) && (!"nbproject".equals(lib.getName())) && (lib.isFolder())) {
+                    if (otherLibs.containsKey(lib.getName())) {
+                        otherLibs.remove(lib.getName());
+                    }
                     Key key = new Key(KeyTypes.LIBRARY, lib);
                     key.direct = true;
                     keys.add(key);
             }
             keys.addAll(libFolders);
         }
+        //XXX get this from "npm root"
+        File userHomeModules = new File(new File(System.getProperty("user.home")), "node_modules");
+        userHomeModules = (userHomeModules.exists()) && (userHomeModules.isDirectory()) ? userHomeModules : null;
+
+        //XXX get this from "npm root -g"
+        File libModules = new File("/usr/lib/node_modules");
+        libModules = (libModules.exists()) && (libModules.isDirectory()) ? libModules : null;
+
+        String src = DefaultExectable.get().getSourcesLocation();
+        File nodeSources = src == null ? null : new File(src);
+        File libDir = nodeSources == null ? null : new File(nodeSources, "lib");
+
+        for (String lib : otherLibs.keySet()) {
+            if ("./".equals(lib)) {
+                continue;
+            }
+            if (userHomeModules != null) {
+                File f = new File(userHomeModules, lib);
+                if ((f.exists()) && (f.isDirectory())) {
+                    Key key = new Key(KeyTypes.LIBRARY, FileUtil.toFileObject(FileUtil.normalizeFile(f)));
+                    key.direct = true;
+                    keys.add(key);
+                    continue;
+                }
+            }
+            if (libModules != null) {
+                File f = new File(libModules, lib);
+                if ((f.exists()) && (f.isDirectory())) {
+                    Key key = new Key(KeyTypes.LIBRARY, FileUtil.toFileObject(FileUtil.normalizeFile(f)));
+                    keys.add(key);
+                    continue;
+                }
+            }
+            if (libDir != null) {
+                File f = new File(libDir, lib + ".js");
+                if ((f.exists()) && (f.isFile()) && (f.canRead())) {
+                    Key key = new Key(KeyTypes.LIBRARY, FileUtil.toFileObject(FileUtil.normalizeFile(f)));
+                    keys.add(key);
+                    continue;
+                }
+            }
+            if (Arrays.binarySearch(builtInNodeLibs, lib) >= 0) {
+                Key key = new NodeProjectSourceNodeFactory.Key.BuiltInLibrary(lib);
+                keys.add(key);
+                key.direct = true;
+                continue;
+            }
+            Key.MissingLibrary key = new Key.MissingLibrary(lib);
+            List<FileObject> referencedBy = otherLibs.get(lib);
+            List<String> paths = new LinkedList<String>();
+            for (FileObject fo : referencedBy) {
+                if (FileUtil.isParentOf(project.getProjectDirectory(), fo)) {
+                    paths.add (FileUtil.getRelativePath(project.getProjectDirectory(), fo));
+                } else {
+                    paths.add(fo.getPath());
+                }
+            }
+            key.references = paths;
+            keys.add(key);
+        }
+
+        for (FileObject fo : flds) {
+            if (fo.getName().equals("node_modules")) {
+                continue;
+            }
+            keys.add(new Key(KeyTypes.SOURCE, fo));
+        }
         return keys;
     }
 
         if (libs != null) {
             for (FileObject fo : libFolder.getChildren()) {
                 for (FileObject lib : fo.getChildren()) {
-                    if (!"node_modules".equals(lib.getName()) && !"nbproject".equals(lib.getName()) && lib.isFolder()) {
+                    if ((!"node_modules".equals(lib.getName())) && (!"nbproject".equals(lib.getName())) && (lib.isFolder())) {
                         boolean jsFound = false;
                         for (FileObject kid : lib.getChildren()) {
                             jsFound = "js".equals(kid.getExt());
         }
     }
 
+    private Map<String, List<FileObject>> findOtherModules(FileObject fld) {
+        Map<String, List<FileObject>> libs = new HashMap<String,List<FileObject>>();
+        assert (!EventQueue.isDispatchThread());
+        for (FileObject fo : NbCollections.iterable(fld.getChildren(true))) {
+            if (("js".equals(fo.getExt())) && (fo.isData()) && (fo.canRead())) {
+                checkForLibraries(fo, libs);
+            }
+        }
+        return libs;
+    }
+
+    private void checkForLibraries(FileObject jsFile,Map<String, List<FileObject>> all) {
+        try {
+            String text = jsFile.asText();
+            Matcher m = CHECK_FOR_REQUIRE.matcher(text);
+            while (m.find()) {
+//                all.add(m.group(1));
+                List<FileObject> l = all.get(m.group(1));
+                if (l == null) {
+                    l = new LinkedList<FileObject>();
+                    all.put(m.group(1), l);
+                }
+                l.add(jsFile);
+            }
+        } catch (IOException ex) {
+            Logger.getLogger(NodeProjectSourceNodeFactory.class.getName()).log(Level.INFO, jsFile.getPath(), ex);
+        }
+    }
+
     @Override
     public void addChangeListener(ChangeListener l) {
         supp.addChangeListener(l);
     }
 
     @Override
-    public Node node(Key key) {
+    public Node node(final Key key) {
         switch (key.type) {
             case LIBRARY:
                 return new LibraryFilterNode(key);
             case SOURCE:
                 return new FilterNode(nodeFromKey(key));
+            case BUILT_IN_LIBRARY:
+                AbstractNode li = new AbstractNode(Children.LEAF);
+                li.setName(key.toString());
+                li.setDisplayName(key.toString());
+                li.setShortDescription("Built-in library '" + key + "'");
+                li.setIconBaseWithExtension("org/netbeans/modules/nodejs/resources/libs.png"); //NOI18N
+                return li;
+            case MISSING_LIBRARY:
+                AbstractNode an = new AbstractNode(Children.LEAF) {
+                    @Override
+                    public String getHtmlDisplayName() {
+                        return "<font color=\"#EE0000\">" + key; //NOI18N
+                    }
+                };
+                an.setName(key.toString());
+                an.setDisplayName(key.toString());
+                StringBuilder sb = new StringBuilder("<html>Missing library <b><i>" + key + "</i></b>");
+                if (key instanceof Key.MissingLibrary && ((Key.MissingLibrary) key).references != null && !((Key.MissingLibrary) key).references.isEmpty()) {
+                    sb.append("<p>Referenced By<br><ul>");
+                    for (String path : ((Key.MissingLibrary) key).references) {
+                        sb.append("<li>").append(path).append("</li>\n");
+                    }
+                    sb.append("</ul></pre></blockquote></html>");
+                }
+                an.setShortDescription(sb.toString());
+                an.setIconBaseWithExtension("org/netbeans/modules/nodejs/resources/libs.png");
+                return an;                
             default:
                 throw new AssertionError();
         }
         //do nothing
     }
 
-    static final class Key {
+    static class Key {
 
         private final KeyTypes type;
         private final FileObject fld;
         }
 
         public String toString() {
-            return type + " " + fld.getName() + (direct ? " direct" : " indirect");
+            return type + " " + fld.getName() + (direct ? " direct" : " indirect"); //NOI18N
         }
+        
+        static class BuiltInLibrary extends Key {
+            private final String name;
+            BuiltInLibrary(String name) {
+                super (KeyTypes.BUILT_IN_LIBRARY, null);
+                this.name = name;
+            }
+            
+            @Override
+            public String toString() {
+                return name;
+            }
+        }
+        
+        static class MissingLibrary extends Key {
+            private final String name;
+            private List<String> references;
+            MissingLibrary(String name) {
+                super (KeyTypes.MISSING_LIBRARY, null);
+                this.name = name;
+            }
+            
+            @Override
+            public String toString() {
+                return name;
+            }
+        }
+        
     }
 
     static enum KeyTypes {
-
         SOURCE,
-        LIBRARY
+        LIBRARY,
+        BUILT_IN_LIBRARY,
+        MISSING_LIBRARY
     }
 
     interface LibrariesFolderFinder {
         public String getHtmlDisplayName() {
             StringBuilder sb = new StringBuilder();
             if (!key.direct) {
-                sb.append("<font color='!controlShadow'>");
+                sb.append("<font color='!controlDkShadow'>");
             }
             sb.append(getDisplayName());
             if (version != null) {
                 sb.append(" <i><font color='#9999AA'> ").append(version).append("</i>");
                 if (!key.direct) {
-                    sb.append("<font color='!controlShadow'>");
+                    sb.append("<font color='!controlDkShadow'>");
                 }
             }
             if (!key.direct) {
             //do nothing
         }
     }
+    private static class FOComparator
+            implements Comparator<FileObject> {
+
+        public int compare(FileObject o1, FileObject o2) {
+            boolean aJs = ("js".equals(o1.getExt())) || ("json".equals(o1.getExt()));
+            boolean bJs = ("js".equals(o2.getExt())) || ("json".equals(o2.getExt()));
+
+            boolean aFld = o1.isFolder();
+            boolean bFld = o2.isFolder();
+
+            if (aJs == bJs) {
+                if (aFld == bFld) {
+                    return o1.getName().compareToIgnoreCase(o2.getName());
+                }
+                if (aFld) {
+                    return 1;
+                }
+                return -1;
+            }
+
+            if (aJs) {
+                return -1;
+            }
+            return 1;
+        }
+    }
 }

nodejs/src/org/netbeans/modules/nodejs/ProjectMetadataImpl.java

 import java.beans.PropertyChangeSupport;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import org.openide.filesystems.FileAlreadyLockedException;
 import org.openide.filesystems.FileChangeAdapter;
 import org.openide.filesystems.FileEvent;
-import org.openide.filesystems.FileLock;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileSystem.AtomicAction;
 import org.openide.filesystems.FileUtil;
                     return map;
                 }
             }
-            FileLock fileLock = fo.lock();
             try {
                 SimpleJSONParser p = new SimpleJSONParser(true); //permissive mode - will parse as much as it can
-                Map<String, Object> m = p.parse(fo);
-                ProjectMetadataImpl.this.hasErrors = err = p.hasErrors();
-                synchronized (this) {
-                    map = Collections.synchronizedMap(m);
-                    return map;
+                InputStream in = fo.getInputStream();
+                try {
+                    Map<String, Object> m = p.parse(fo);
+                    ProjectMetadataImpl.this.hasErrors = err = p.hasErrors();
+                    synchronized (this) {
+                        map = Collections.synchronizedMap(m);
+                        return map;
+                    }
+                } finally {
+                    in.close();
                 }
             } catch (JsonException ex) {
                 Logger.getLogger(ProjectMetadataImpl.class.getName()).log(Level.INFO,
                         "Bad package.json in " + fo.getPath(), ex);
                 return new LinkedHashMap<String, Object>();
-            } finally {
-                fileLock.releaseLock();
             }
         } finally {
             lock.unlock();
         }
     }
 
-    private final Map<String, Object> getMap() {
+    public final Map<String, Object> getMap() {
         Map<String, Object> result = map;
         if (result == null) {
             synchronized (this) {
         }
         if (result == null) {
             final FileObject fo = project.getProjectDirectory().getFileObject("package.json");
+            System.out.println("GOT FO " + fo);
             if (fo == null) {
+                System.err.println("No package.json");
                 return new LinkedHashMap<String, Object>();
             }
             if (!listening) {
                 fo.addFileChangeListener(FileUtil.weakFileChangeListener(this, fo));
             }
             try {
+                System.out.println("LOAD " + fo);
                 result = load(fo);
                 synchronized (this) {
                     map = result;
                 }
             } catch (IOException ioe) {
-                Logger.getLogger(ProjectMetadataImpl.class.getName()).log(Level.INFO,
+                Logger.getLogger(ProjectMetadataImpl.class.getName()).log(Level.WARNING,
                         "Problems loading " + fo.getPath(), ioe);
                 result = new LinkedHashMap<String,Object>();
             }

nodejs/src/org/netbeans/modules/nodejs/json/SimpleJSONParser.java

     }
 
     public Map<String, Object> parse(FileObject in) throws JsonException, IOException {
-        return parse(in.asText());
+        return parse(in.asText("UTF-8"));
     }
 
     public Map<String, Object> parse(InputStream in) throws JsonException, IOException {
-        return parse(in, null);
+        return parse(in, "UTF-8");
     }
 
     public Map<String, Object> parse(InputStream in, String encoding) throws JsonException, IOException {
     private final class CharVisitor {
 
         StringBuilder sb = new StringBuilder();
-        S s = BEGIN;
-        Stack<S> awaits = new Stack<S>();
+        S s = S.BEGIN;
+        Stack<S> awaits = new Stack();
+        char lastChar;
+        S stateBeforeComment;
 
         void setState(S s, char c, int pos) {
             stateChange(this.s, s, c, pos);
             this.s = s;
         }
-        char lastChar;
-        S stateBeforeComment;
 
         void visitChar(char c, int pos, int line, State state) throws JsonException {
             if (c == '/' && s != IN_ARRAY_ELEMENT && s != IN_KEY && s != IN_VALUE && s != AWAIT_BEGIN_COMMENT && s != IN_COMMENT && s != IN_LINE_COMMENT) {
                                 setState(AWAITING_COMPOUND_VALUE, c, pos);
                                 state.enterCompoundValue();
                                 break;
+                            case 'f':
+                            case 't':
+                                sb.append(c);
+                                setState(IN_BOOLEAN_VALUE, c, pos);
+                                break;
+                            case '-':
+                            case '.':
+                            case '0':
+                            case '1':
+                            case '2':
+                            case '3':
+                            case '4':
+                            case '5':
+                            case '6':
+                            case '7':
+                            case '8':
+                            case '9':
+                                sb.append(c);
+                                setState(IN_NUMERIC_VALUE, c, pos);
+                                break;
                             default:
                                 error("Expected '\"' or ':' to start value", c, line, pos);
                         }
                             case '"':
                                 setState(S.IN_ARRAY_ELEMENT, c, pos);
                                 break;
+                            case 'f':
+                            case 't':
+                                setState(IN_BOOLEAN_ARRAY_ELEMENT, c, pos);
+                                sb.append(c);
+                                break;
+                            case '-':
+                            case '.':
+                            case '0':
+                            case '1':
+                            case '2':
+                            case '3':
+                            case '4':
+                            case '5':
+                            case '6':
+                            case '7':
+                            case '8':
+                            case '9':
+                                setState(IN_NUMERIC_ARRAY_ELEMENT, c, pos);
+                                sb.append(c);
+                                break;
                             case ']':
                                 setState(AFTER_VALUE, c, pos);
                                 state.exitArrayValue();
                         }
                         sb.append(c);
                         break;
+                    case IN_NUMERIC_ARRAY_ELEMENT:
+                        if ((Character.isWhitespace(c)) || (c == ',') || (c == ']')) {
+                            if (sb.length() > 0) {
+                                state.numericArrayElement(sb.toString());
+                                sb.setLength(0);
+                            }
+                            if (Character.isWhitespace(c)) {
+                                setState(AFTER_ARRAY_ELEMENT, c, pos);
+                            } else {
+                                setState(c == ']' ? AFTER_VALUE : AWAITING_ARRAY_ELEMENT, c, pos);
+                                if (c == ']') {
+                                    state.exitArrayValue();
+                                }
+                            }
+                            return;
+                        }
+                        if ((c != '.') && (c != '-') && (!Character.isDigit(c))) {
+                            error("Invalid character in numeric array element: ", c, line, pos);
+                        } else {
+                            sb.append(c);
+                        }
+
+                        break;
+                    case IN_BOOLEAN_ARRAY_ELEMENT:
+                        if ((Character.isWhitespace(c)) || (c == ',') || (c == ']')) {
+                            if (sb.length() > 0) {
+                                state.booleanArrayElement(sb.toString());
+                                sb.setLength(0);
+                            }
+                            if (Character.isWhitespace(c)) {
+                                setState(AFTER_ARRAY_ELEMENT, c, pos);
+                            } else {
+                                setState(c == ']' ? AFTER_VALUE : AWAITING_ARRAY_ELEMENT, c, pos);
+                                if (c == ']') {
+                                    state.exitArrayValue();
+                                }
+                            }
+                            return;
+                        }
+                        if ((!"true".startsWith(sb.toString())) && (!"false".startsWith(sb.toString()))) {
+                            error("Invalid character in boolean array element for '" + this.sb + "': ", c, line, pos);
+                        } else {
+                            sb.append(c);
+                        }
+
+                        break;
+                    case IN_NUMERIC_VALUE:
+                        if ((Character.isWhitespace(c)) || (c == ',')) {
+                            setState(AWAITING_KEY, c, pos);
+                            state.numberValue(sb.toString());
+                            sb.setLength(0);
+                            return;
+                        }
+                        if ((Character.isDigit(c)) || (c == '.') || (c == '-')) {
+                            if ((sb.indexOf(".") >= 0) && (c == '.')) {
+                                error("Extra decimal in number: ", c, line, pos);
+                            } else {
+                                sb.append(c);
+                            }
+                        } else {
+                            error("Invalid character in number: ", c, line, pos);
+                        }
+                        break;
+                    case IN_BOOLEAN_VALUE:
+                        if ((Character.isWhitespace(c)) || (c == ',')) {
+                            setState(AWAITING_KEY, c, pos);
+                            state.booleanValue(sb.toString());
+                            sb.setLength(0);
+                            return;
+                        }
+                        char lc = sb.length() == 0 ? '\000' : sb.charAt(sb.length() - 1);
+                        switch (c) {
+                            case 'r':
+                                if (lc != 't') {
+                                    error("Invalid character in boolean - lc=" + lc + ": " + this.sb, c, line, pos);
+                                } else {
+                                    sb.append(c);
+                                }
+                                break;
+                            case 'u':
+                                if (lc != 'r') {
+                                    error("Invalid character in boolean: " + this.sb + " lc is " + lc + " - ", c, line, pos);
+                                } else {
+                                    sb.append(c);
+                                }
+                                break;
+                            case 'e':
+                                if ((lc != 'u') && (lc != 's')) {
+                                    error("Invalid character in boolean: ", c, line, pos);
+                                } else {
+                                    sb.append(c);
+                                }
+                                break;
+                            case 'a':
+                                if (lc != 'f') {
+                                    error("Invalid character in boolean: ", c, line, pos);
+                                } else {
+                                    sb.append(c);
+                                }
+                                break;
+                            case 'l':
+                                if (lc != 'a') {
+                                    error("Invalid character in boolean: ", c, line, pos);
+                                } else {
+                                    sb.append(c);
+                                }
+                                break;
+                            case 's':
+                                if (lc != 'l') {
+                                    error("Invalid character in boolean: ", c, line, pos);
+                                } else {
+                                    sb.append(c);
+                                }
+                                break;
+                            default:
+                                error("Invalid character in boolean: ", c, line, pos);
+                        }
+                        break;
                     case IN_VALUE:
                         if (c == '"') {
                             setState(S.AFTER_VALUE, c, pos);
         }
 
         private void stateChange(S s, S to, char c, int pos) {
-//            System.out.println("from " + s + " to " + to + " " + c + " at " + pos);
         }
     }
 
-    enum S {
+    public enum S {
 
         AWAIT_BEGIN_COMMENT,
         IN_COMMENT,
         IN_LINE_COMMENT,
         IN_KEY,
         IN_VALUE,
+        IN_NUMERIC_VALUE,
+        IN_BOOLEAN_VALUE,
         IN_ARRAY_ELEMENT,
+        IN_BOOLEAN_ARRAY_ELEMENT,
+        IN_NUMERIC_ARRAY_ELEMENT,
         BEGIN,
         AWAITING_KEY,
         AWAITING_COMPOUND_VALUE,
             }
             return AWAITING_KEY;
         }
+        
+
+        private void numericArrayElement(String value) {
+            try {
+                String key = (String) this.currKey.peek();
+                List l = null;
+                if ((l == null) && (!this.currList.isEmpty())) {
+                    l = (List) this.currList.peek();
+                } else if (l == null) {
+                    throw new SimpleJSONParser.Internal("No array present for array value " + value);
+                }
+                l.add(toNumber(value));
+            } catch (NumberFormatException nfe) {
+                throw new SimpleJSONParser.Internal("Bad number '" + value + "'");
+            }
+        }
+
+        private void booleanArrayElement(String value) {
+            String key = (String) this.currKey.peek();
+            List l = null;
+            if ((l == null) && (!this.currList.isEmpty())) {
+                l = (List) this.currList.peek();
+            } else if (l == null) {
+                throw new SimpleJSONParser.Internal("No array present for array value " + value);
+            }
+            if ("true".equals(value)) {
+                l.add(Boolean.valueOf(true));
+            } else if ("false".equals(value)) {
+                l.add(Boolean.valueOf(false));
+            } else {
+                throw new SimpleJSONParser.Internal("Illegal boolean value '" + value + "'");
+            }
+        }
+        
+        private void booleanValue(String s) {
+            if ("true".equals(s)) {
+                String key = (String) this.currKey.pop();
+                this.curr.put(key, Boolean.TRUE);
+            } else if ("false".equals(s)) {
+                String key = (String) this.currKey.pop();
+                this.curr.put(key, Boolean.FALSE);
+            } else {
+                throw new SimpleJSONParser.Internal("Invalid boolean '" + s + "'");
+            }
+        }
+
+        private Number toNumber(String toString) {
+            Number n;
+            if (toString.indexOf(".") >= 0) {
+                n = Double.valueOf(Double.parseDouble(toString));
+                if (n.floatValue() == n.doubleValue()) {
+                    n = Float.valueOf(n.floatValue());
+                }
+            } else {
+                n = Long.valueOf(Long.parseLong(toString));
+                if (n.longValue() == n.intValue()) {
+                    n = Integer.valueOf(n.intValue());
+                }
+            }
+            return n;
+        }
+
+        private void numberValue(String toString) {
+            try {
+                String key = (String) this.currKey.pop();
+                this.curr.put(key, toNumber(toString));
+            } catch (NumberFormatException nfe) {
+                throw new SimpleJSONParser.Internal("Invalid number '" + toString + "'");
+            }
+        }        
 
         public void enterCompoundValue() {
             String key = currKey.isEmpty() ? null : currKey.peek();
         Arrays.fill(indentChars, ' ');
         String ind = new String(indentChars);
         String indl = ind + "    ";
-        sb.append('\n').append(ind).append('[').append('\n');
-        for (Iterator<Object> it = l.iterator(); it.hasNext();) {
+        boolean inline = l.isEmpty() || l.get(0) instanceof Boolean || l.get(0) instanceof Number;
+        if (!inline) {
+            sb.append('\n').append(ind);
+        }
+        sb.append('[');
+        if (!inline) {
+            sb.append('\n');
+        }
+        int ix = 0;
+        for (Iterator it = l.iterator(); it.hasNext();) {
             Object o = it.next();
             if (o instanceof Map) {
                 Map<String, Object> mm = (Map<String, Object>) o;
                 out(mm, sb, indent + 1);
             } else if (o instanceof List) {
                 out((List) o, sb, indent + 1);
-            } else if (o instanceof CharSequence) {
-                String s = ("" + o).replace("\"", "\\\"");
+            } else if (((o instanceof Number)) || ((o instanceof Boolean))) {
+                sb.append(o);
+                if (it.hasNext()) {
+                    sb.append(',');
+                }
+            } else if ((o instanceof CharSequence)) {
+                String s = new StringBuilder().append("").append(o).toString().replace("\"", "\\\"");
                 sb.append(indl).append('"').append(s).append('"');
                 if (it.hasNext()) {
                     sb.append(',');
                     reflectOut(o, sb, indent + 1);
                 }
             }
+            ix++;
         }
-        sb.append(ind).append(']');
+        if (!inline) {
+            sb.append(ind);
+        }
+        sb.append(']');
     }
 
     private static final void reflectOut(Object o, StringBuilder sb, int indent) {
                 out((Map<String, Object>) e.getValue(), sb, indent + 1);
                 sb.append(ind);
                 sb.append('}');
+            } else if (((e.getValue() instanceof Number)) || ((e.getValue() instanceof Boolean))) {
+                sb.append(e.getValue());
             } else if (e.getValue().getClass().isArray()) {
                 if (e.getValue().getClass().getComponentType().isPrimitive()) {
                     Object[] o = Utilities.toObjectArray(e.getValue());

nodejs/src/org/netbeans/modules/nodejs/libraries/LibrariesPanel.java

             }
         }
     }
-    private static final Pattern p = Pattern.compile(
-            "(\\S+)\\s+(.*)=(\\S+)"); //NOI18N
-
+    public static final Pattern p = Pattern.compile(
+            "^(\\S+)\\s+(.*?)=(\\S+).*?$", Pattern.MULTILINE | Pattern.DOTALL); //NOI18N
+    
     private void publish(CharSequence seq) {
         final Matcher m = p.matcher(seq);
         Process p;

nodejs/test/unit/src/org/netbeans/modules/nodejs/json/MetadataTest.java

  * Portions Copyrighted 2011 Sun Microsystems, Inc.
  */
 package org.netbeans.modules.nodejs.json;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
 import org.netbeans.modules.nodejs.ProjectMetadataImpl;
 
 import org.junit.Test;
 import static org.junit.Assert.*;
 import org.netbeans.api.project.Project;
-import org.netbeans.modules.nodejs.ProjectMetadata;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
 import org.openide.util.Lookup;
  */
 public class MetadataTest {
     Fake fake = new Fake();
-    ProjectMetadata impl = new ProjectMetadataImpl(fake);
+    ProjectMetadataImpl impl = new ProjectMetadataImpl(fake);
     
     @Test
+    public void testLoading() {
+        Map m = impl.getMap();
+        System.out.println("GOT " + m);
+        assertNotNull(m);
+        assertFalse(m.isEmpty());
+        assertEquals("recon", impl.getValue("name"));
+        assertEquals("0.0.8", impl.getValue("version"));
+        assertEquals("git", impl.getValue("repository.type"));
+    }
+
+    @Test
     public void test() {
         impl.setValue("name", "thing");
         assertEquals ("thing", impl.getValue("name"));
     
     static class Fake implements Project {
         FileObject root = FileUtil.createMemoryFileSystem().getRoot();
+        
+        Fake() {
+            try {
+                InputStream in = MetadataTest.class.getResourceAsStream("package_0.json");
+                try {
+                    FileObject fo = root.createData("package.json");
+                    OutputStream out = fo.getOutputStream();
+                    try {
+                        FileUtil.copy(in, out);
+                    } finally {
+                        out.close();
+                    }
+                } finally {
+                    in.close();
+                }
+            } catch (IOException ex) {
+                throw new Error(ex);
+            }
+        }
 
         public FileObject getProjectDirectory() {
             return root;

nodejs/test/unit/src/org/netbeans/modules/nodejs/json/SimpleJSONParserTest.java

 import java.io.IOException;
 import java.util.Map;
 import java.io.InputStream;
+import java.util.List;
 import org.junit.Test;
 import static org.junit.Assert.*;
 import org.netbeans.modules.nodejs.json.SimpleJSONParser.JsonException;
 
     @Test
     public void testParse() throws IOException, JsonException {
-        for (int i = 0; i < 10; i++) {
+        for (int i = 0; i < 11; i++) {
             parseJSON("package_" + i + ".json");
         }
         for (int i = 0; i < 4; i++) {
             }
         }
     }
+    
+    @Test
+    public void testIntAndBool() throws Exception {
+        String t = "{ \"foo\": 23, \"bar\": true, \"baz\" : [5,10,15,20], \"quux\": [true,false,false,true]  }";
+        Map<String,Object> m = new SimpleJSONParser().parse(t);
+        assertNotNull(m.get("foo"));
+        assertNotNull(m.get("bar"));
+        assertNotNull(m.get("baz"));
+        assertNotNull(m.get("quux"));
+        assertTrue (m.get("foo") instanceof Integer);
+        assertTrue (m.get("bar") instanceof Boolean);
+        assertTrue (m.get("baz") instanceof List);
+        assertTrue (m.get("baz") instanceof List);
+        
+        CharSequence nue = new SimpleJSONParser().toJSON(m);
+        Map<String,Object> m1 = new SimpleJSONParser().parse(nue);
+        assertEquals(m, m1);
+    }
 
     private void parseJSON(String what) throws IOException, JsonException {
         System.out.println("-------------------------------");

nodejs/test/unit/src/org/netbeans/modules/nodejs/json/package_10.json

+{
+    "description" : "A thing to test flatiron",
+    "version" : "0.0.0",
+    "flah" : true,
+    "fbooger" : [12,3,4,false],
+    "dependencies" : {
+        "union" : "0.3.0",
+        "flatiron" : "0.2.2"
+    },
+    "devDependencies" : {
+        "api-easy" : "0.3.2",
+        "vows" : "0.6.1"
+    },
+    "scripts" : {
+        "test" : "vows --spec",
+        "start" : "node app.js"
+    },
+    "name" : "doohickey",
+    "author" : {
+        "name" : "Tim Boudreau",
+        "email" : "niftiness@gmail.com"
+    },
+    "homepage" : "http://timboudreau.com",
+    "keywords" : 
+        [
+            "food"
+        ],
+    "bugs" : {
+        "web" : "null"
+    },
+    "main" : "app.js",
+    "license" : {
+        "type" : "bsd"
+    }
+}