changeset 7:f201bb903e32 6.0

7901205: Add assembler support (JASM, JDIS, JCOD, JDEC) for Modules 7901521: JASM reports warnings for interface methods with code in 52.0 class files Summary: Changes were made to both Assembler/Disassembler pairs to support the Modules Attribute. Jasm syntax is updated to encode Mod-info. Reviewed-by: klooney Contributed-by: Leonid Kuskov <leonid.kuskov@oracle.com>
author bkurotsu
date Wed, 02 Mar 2016 14:00:55 -0700
parents d0e8d77ad32e
children b884ce1fc0f8
files .hgignore build/build.xml src/org/openjdk/asmtools/jasm/ClassData.java src/org/openjdk/asmtools/jasm/JasmTokens.java src/org/openjdk/asmtools/jasm/Modifiers.java src/org/openjdk/asmtools/jasm/ModuleAttr.java src/org/openjdk/asmtools/jasm/Parser.java src/org/openjdk/asmtools/jasm/ParserCP.java src/org/openjdk/asmtools/jasm/RuntimeConstants.java src/org/openjdk/asmtools/jasm/Tables.java src/org/openjdk/asmtools/jasm/TypeAnnotationUtils.java src/org/openjdk/asmtools/jasm/i18n.properties src/org/openjdk/asmtools/jcoder/JcodTokens.java src/org/openjdk/asmtools/jcoder/Jcoder.java src/org/openjdk/asmtools/jdec/ClassData.java src/org/openjdk/asmtools/jdis/ClassData.java src/org/openjdk/asmtools/jdis/ConstantPool.java src/org/openjdk/asmtools/jdis/MethodData.java src/org/openjdk/asmtools/jdis/ModuleData.java
diffstat 19 files changed, 1311 insertions(+), 166 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Wed Mar 02 14:00:55 2016 -0700
@@ -0,0 +1,7 @@
+re:data/.*
+re:asmtools-6.0-build/.*
+re:.*~$
+re:.*\.log$
+re:.*\.pyc$
+re:.*\.swp$
+re:.*\.DS_Store$
--- a/build/build.xml	Fri Dec 19 11:29:43 2014 -0700
+++ b/build/build.xml	Wed Mar 02 14:00:55 2016 -0700
@@ -246,8 +246,8 @@
     <target name="compileClasses" depends="prepare">
         <mkdir dir="${build.classes.dir}"/>
         <javac fork="true"
-            target="1.7"
-            source="1.7"
+            target="1.8"
+            source="1.8"
             srcdir="${build.src.classes.dir}"
             destdir="${build.classes.dir}"
             debug="${javac.debug}"
--- a/src/org/openjdk/asmtools/jasm/ClassData.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jasm/ClassData.java	Wed Mar 02 14:00:55 2016 -0700
@@ -22,11 +22,12 @@
  */
 package org.openjdk.asmtools.jasm;
 
+import java.io.*;
+import java.util.ArrayList;
+
 import static org.openjdk.asmtools.jasm.Constants.DEFAULT_MAJOR_VERSION;
 import static org.openjdk.asmtools.jasm.Constants.DEFAULT_MINOR_VERSION;
 import static org.openjdk.asmtools.jasm.Tables.*;
-import java.io.*;
-import java.util.ArrayList;
 
 /**
  * ClassData
@@ -52,10 +53,11 @@
     ArrayList<MethodData> methods = new ArrayList<>();
     DataVectorAttr<InnerClassData> innerClasses = null;
     DataVectorAttr<BootstrapMethodData> bootstrapMethodsAttr = null;
-    String mdl = null;
+    ModuleAttr moduleAttribute = null;
     Environment env;
     protected ConstantPool pool;
 
+
     private static final String DEFAULT_EXTENSION = ".class";
     String fileExtension = DEFAULT_EXTENSION;
     public CDOutputStream cdos;
@@ -86,6 +88,12 @@
         this.interfaces = interfaces;
     }
 
+    public final void initAsModule(ConstantPool.ConstCell me) {
+        this.access = RuntimeConstants.ACC_MODULE;
+        this.me = me;
+        this.father = new ConstantPool.ConstCell(0);
+    }
+
     /**
      * default constructor
      *
@@ -101,6 +109,7 @@
 
     }
 
+
     /**
      * canonical default constructor
      *
@@ -310,6 +319,13 @@
         }
     }
 
+    public void endModule(ModuleAttr moduleAttr) {
+        moduleAttribute = moduleAttr.build();
+        pool.NumberizePool();
+        pool.CheckGlobals();
+        myClassName = "module-info";
+    }
+
     private void printInnerClasses() {
         if (innerClasses != null) {
             int i = 1;
@@ -370,32 +386,29 @@
             out.writeShort(0);
         }
 
+        DataVector attrs = new DataVector();
+
         // Write the attributes
-        DataVector attrs = new DataVector();
-        attrs.add(sourceFileNameAttr);
-        if (innerClasses != null) {
-            attrs.add(innerClasses);
-        }
-        if (syntheticAttr != null) {
-            attrs.add(syntheticAttr);
-        }
-        if (deprecatedAttr != null) {
-            attrs.add(deprecatedAttr);
-        }
-        if (annotAttrVis != null) {
-            attrs.add(annotAttrVis);
-        }
-        if (annotAttrInv != null) {
-            attrs.add(annotAttrInv);
-        }
-        if (type_annotAttrVis != null) {
-            attrs.add(type_annotAttrVis);
-        }
-        if (type_annotAttrInv != null) {
-            attrs.add(type_annotAttrInv);
-        }
-        if (bootstrapMethodsAttr != null) {
-            attrs.add(bootstrapMethodsAttr);
+        if( moduleAttribute != null ) {
+            attrs.add(moduleAttribute);
+        } else {
+            attrs.add(sourceFileNameAttr);
+            if (innerClasses != null)
+                attrs.add(innerClasses);
+            if (syntheticAttr != null)
+                attrs.add(syntheticAttr);
+            if (deprecatedAttr != null)
+                attrs.add(deprecatedAttr);
+            if (annotAttrVis != null)
+                attrs.add(annotAttrVis);
+            if (annotAttrInv != null)
+                attrs.add(annotAttrInv);
+            if (type_annotAttrVis != null)
+                attrs.add(type_annotAttrVis);
+            if (type_annotAttrInv != null)
+                attrs.add(type_annotAttrInv);
+            if (bootstrapMethodsAttr != null)
+                attrs.add(bootstrapMethodsAttr);
         }
         attrs.write(out);
     } // end ClassData.write()
--- a/src/org/openjdk/asmtools/jasm/JasmTokens.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jasm/JasmTokens.java	Wed Mar 02 14:00:55 2016 -0700
@@ -294,7 +294,15 @@
         BRIDGE              (171, "BRIDGE",     "bridge",   TokenType.MODIFIER, KeywordType.KEYWORD),
 
         // Declaration keywords
-        BOOTSTRAPMETHOD     (172, "BOOTSTRAPMETHOD", "BootstrapMethod", TokenType.DECLARATION, KeywordType.KEYWORD, true);
+        BOOTSTRAPMETHOD     (172, "BOOTSTRAPMETHOD", "BootstrapMethod", TokenType.DECLARATION, KeywordType.KEYWORD, true),
+
+        //Module statements
+        REQUIRES            (180, "REQUIRES", "requires", TokenType.DECLARATION, KeywordType.KEYWORD, true),
+        EXPORTS             (182, "EXPORTS",  "exports",  TokenType.DECLARATION, KeywordType.KEYWORD, true),
+        TO                  (183, "TO",       "to",       TokenType.DECLARATION, KeywordType.KEYWORD, true),
+        USES                (184, "USES",     "uses",     TokenType.DECLARATION, KeywordType.KEYWORD, true),
+        PROVIDES            (185, "PROVIDES", "provides", TokenType.DECLARATION, KeywordType.KEYWORD, true),
+        WITH                (186, "WITH",     "with",     TokenType.DECLARATION, KeywordType.KEYWORD, true);
 
         // Misc Keywords
         private Integer value;
--- a/src/org/openjdk/asmtools/jasm/Modifiers.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jasm/Modifiers.java	Wed Mar 02 14:00:55 2016 -0700
@@ -40,14 +40,16 @@
     public static final int MM_ATTR        = SYNTHETIC_ATTRIBUTE | DEPRECATED_ATTRIBUTE;
 
     public static final int MM_INTRF       = ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE | MM_ATTR ; // | ACC_MODULE  ;
-    public static final int MM_CLASS       = ACC_PUBLIC | ACC_FINAL|  ACC_SUPER | ACC_ABSTRACT | ACC_SYNTHETIC  | ACC_ANNOTATION | ACC_ENUM | MM_ATTR ; // |  ACC_MODULE ;
+    public static final int MM_CLASS       = ACC_PUBLIC | ACC_FINAL|  ACC_SUPER | ACC_ABSTRACT | ACC_SYNTHETIC  | ACC_ANNOTATION | ACC_ENUM | MM_ATTR |  ACC_MODULE ;
     public static final int MM_ACCESS      = ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED; // | ACC_MODULE;
     public static final int MM_FIELD       = MM_ACCESS | ACC_STATIC | ACC_FINAL |  ACC_VOLATILE | ACC_TRANSIENT |  ACC_SYNTHETIC | ACC_ENUM | MM_ATTR ; // |  ACC_MODULE ;
-    public static final int MM_I_METHOD    = ACC_ABSTRACT | ACC_PUBLIC | ACC_VARARGS | ACC_BRIDGE | ACC_SYNTHETIC ; // interface method
+    public static final int MM_I_METHOD    = ACC_ABSTRACT | ACC_PUBLIC | ACC_PRIVATE | ACC_STATIC | ACC_VARARGS | ACC_BRIDGE | ACC_SYNTHETIC ; // interface method
     public static final int MM_A_METHOD    = MM_ACCESS | ACC_ABSTRACT | MM_ATTR;
     public static final int MM_N_METHOD    = MM_ACCESS | ACC_STRICT | ACC_VARARGS | ACC_SYNTHETIC | MM_ATTR;  // <init>
     public static final int MM_METHOD      = MM_ACCESS | ACC_STATIC | ACC_FINAL | ACC_SYNCHRONIZED |  ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE | ACC_ABSTRACT |  ACC_STRICT | ACC_SYNTHETIC | MM_ATTR ; // |  ACC_MODULE ;
     public static final int MM_INNERCLASS  = MM_ACCESS | ACC_STATIC | ACC_FINAL | ACC_SUPER | ACC_INTERFACE | ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM | MM_ATTR ; // |  ACC_MODULE ;
+    public static final int MM_REQUIRES    = ACC_REEXPORT | ACC_SYNTHETIC | ACC_MANDATED ;
+
 
     private Modifiers() {
     }
@@ -59,6 +61,10 @@
         return ref;
     }
 
+    public static boolean validRequires(int mod) {
+        return (mod & ~MM_REQUIRES) == 0;
+    }
+
     public static boolean validClass(int mod) {
         return (mod & ~MM_CLASS) == 0;
     }
@@ -87,9 +93,9 @@
         return (mod & ~MM_N_METHOD) == 0;
     }
 
-    public static boolean validInterfaceMethod(int mod) {
-//        return (mod & ~MM_ATTR) == MM_I_METHOD;
-        return ((mod & ~MM_I_METHOD) == 0) && isPublic(mod) && isAbstract(mod);
+    public static boolean validInterfaceMethod(int mod, ClassData cd) {
+        return ((mod & ~MM_I_METHOD) == 0) &&
+            (cd.major_version >= 52 || isPublic(mod) && isAbstract(mod) && !isStatic(mod));
     }
 
     public static boolean validInterfaceField(int mod) {
@@ -155,15 +161,10 @@
     public static boolean isSuper(int mod) {
         return (mod & ACC_SUPER) != 0;
     }
-    /*
-     public static boolean isModule(int mod) {
-     return (mod & ACC_MODULE)!=0;
-     }
-     * */
 
-    public static boolean isMandated(int mod) {
-        return (mod & ACC_MANDATED) != 0;
-    }
+    public static boolean isModule(int mod) { return (mod & ACC_MODULE)!=0; }
+
+    public static boolean isMandated(int mod) { return (mod & ACC_MANDATED) != 0; }
 
     public static boolean isSynchronized(int mod) {
         return (mod & ACC_SYNCHRONIZED) != 0;
@@ -193,6 +194,7 @@
         return isSyntheticPseudoMod(mod) || isDeprecatedPseudoMod(mod);
     }
 
+    public static boolean isReexport(int mod) { return (mod & ACC_REEXPORT) != 0;  }
     /*
      * Checks that only one (or none) of the Access flags are set.
      */
@@ -288,7 +290,7 @@
             if (cd.isInterface()) {
                 if (is_init) {
                     env.error(pos, "warn.init.in_int");
-                } else if (!validInterfaceMethod(mod)) {
+                } else if (!validInterfaceMethod(mod, cd)) {
                     int badflags = (mod & ~MM_I_METHOD);
                     env.error(pos, "warn.invalid.modifier.intmth", toString(badflags, CF_Context.CTX_METHOD)
                             + "   *****" + toString(mod, CF_Context.CTX_METHOD) + "*****");
@@ -314,7 +316,6 @@
                 }
             }
         }
-
     }
 
     /**
@@ -338,8 +339,9 @@
 
     private static StringBuffer _accessString(int mod, CF_Context context) {
         StringBuffer sb = new StringBuffer();
- //       if (isModule(mod))
-        //           sb.append(Tables.keywordName(Tables.Module) + " ");
+        if (context == CF_Context.CTX_CLASS && isModule(mod)) {
+            sb.append(Token.MODULE.parsekey() + " ");
+        }
         if (isPublic(mod)) {
             sb.append(Token.PUBLIC.parsekey() + " ");
         }
@@ -398,7 +400,7 @@
         if (isEnum(mod)) {
             sb.append(Token.ENUM.parsekey() + " ");
         }
-        if (isMandated(mod)) {
+        if (context == CF_Context.CTX_METHOD && isMandated(mod)) {
             sb.append(Token.MANDATED.parsekey() + " ");
         }
 //      We don't have print identifiers for annotation flags
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openjdk/asmtools/jasm/ModuleAttr.java	Wed Mar 02 14:00:55 2016 -0700
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2016, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.asmtools.jasm;
+
+import java.io.IOException;
+import java.util.*;
+
+import static org.openjdk.asmtools.jasm.RuntimeConstants.ACC_NONE;
+import static org.openjdk.asmtools.jasm.RuntimeConstants.ACC_REEXPORT;
+
+public class ModuleAttr extends AttrData {
+  // shared data
+  private final Module.Builder builder;
+  private final ClassData clsData;
+  private Module module;
+
+  ModuleAttr(ClassData cdata) {
+    super(cdata, Tables.AttrTag.ATT_Module.parsekey());
+    builder = new Module.Builder();
+    clsData = cdata;
+  }
+
+  public ModuleAttr build() {
+    Content.instance.requiresStruct = new RequiresStruct();
+    Content.instance.exportsStruct = new ExportsStruct();
+    Content.instance.usesStruct = new UsesStruct();
+    Content.instance.providesStruct = new ProvidesStruct();
+    return this;
+  }
+
+  @Override
+  public int attrLength() { return Content.instance.getLength(); }
+
+  @Override
+  public void write(CheckedDataOutputStream out) throws IOException {
+    super.write(out);
+    Content.instance.write(out);
+  }
+
+  /**
+   * Adds a module on which the current module has a dependence.
+   */
+  protected void require(String d, boolean reexports) {
+    builder.require(d, reexports);
+  }
+
+  /**
+   * Adds a qualified/an unqualified  module exports.
+   */
+  protected void exports(String p, Set<String> ms) { builder.exports(p, ms); }
+
+  /**
+   * Adds a service dependence's of this module
+   */
+  protected void use(String cn) {
+    builder.use(cn);
+  }
+
+  /**
+   * Adds a service that a module provides one implementation of.
+   */
+  protected void provide(String s, String impl) {
+    builder.provide(s, impl);
+  }
+
+  /**
+   * Gets the module if it is absent builds one
+   *
+   * @return the Module
+   */
+  private Module buildModuleIfAbsent() {
+    if (module == null) {
+      module = builder.build();
+    }
+    return module;
+  }
+
+  private enum Content implements Data {
+
+    instance {
+      @Override
+      public int getLength() {
+        return requiresStruct.getLength() + exportsStruct.getLength() +
+            usesStruct.getLength() + providesStruct.getLength();
+      }
+
+      @Override
+      public void write(CheckedDataOutputStream out) throws IOException {
+        // keep order!
+        requiresStruct.write(out);
+        exportsStruct.write(out);
+        usesStruct.write(out);
+        providesStruct.write(out);
+      }
+    };
+    RequiresStruct requiresStruct;
+    ExportsStruct exportsStruct;
+    UsesStruct usesStruct;
+    ProvidesStruct providesStruct;
+  }
+
+  private final static class Module {
+    //* A service dependence's of this module
+    final Set<String> uses;
+    //* A module on which the current module has a dependence.
+    private final Set<Dependence> requires;
+    //* A module export, may be qualified or unqualified.
+    private final Map<String, Set<String>> exports;
+    //* A service that a module provides one or more implementations of.
+    private final Map<String, Set<String>> provides;
+
+    private Module(Set<Dependence> requires,
+                   Map<String, Set<String>> exports,
+                   Set<String> uses,
+                   Map<String, Set<String>> provides) {
+      this.requires = Collections.unmodifiableSet(requires);
+      this.exports = Collections.unmodifiableMap(exports);
+      this.uses = Collections.unmodifiableSet(uses);
+      this.provides = Collections.unmodifiableMap(provides);
+    }
+
+    //* A module on which the current module has a dependence.
+    private final static class Dependence implements Comparable<Dependence> {
+      private final String mn;
+      private final boolean reexports;
+
+      public Dependence(String name, boolean reexports) {
+        this.mn = name;
+        this.reexports = reexports;
+      }
+
+      /**
+       * Returns the module name.
+       */
+      public String name() { return mn; }
+
+      /**
+       * Returns the public modifier of the requires.
+       */
+      public boolean isReexports() { return reexports; }
+
+      @Override
+      public int hashCode() {
+        return mn.hashCode() * 11 + Boolean.hashCode(reexports);
+      }
+
+      @Override
+      public boolean equals(Object o) {
+        if (o instanceof Dependence) {
+          Dependence d = (Dependence) o;
+          return this.mn.equals(d.mn) && Boolean.compare(reexports, d.isReexports()) == 0;
+        }
+        return false;
+      }
+
+      @Override
+      public int compareTo(Dependence o) {
+        int rc = this.mn.compareTo(o.mn);
+        return rc != 0 ? rc : Boolean.compare(reexports, o.isReexports());
+      }
+    }
+
+    /**
+     * The module builder.
+     */
+    private static final class Builder {
+      public final Set<Dependence> requires = new HashSet<>();
+      final Map<String, Set<String>> exports = new HashMap<>();
+      final Set<String> uses = new HashSet<>();
+      final Map<String, Set<String>> provides = new HashMap<>();
+
+      public Builder() {
+      }
+
+      public Builder require(String d, boolean reexports) {
+        requires.add(new Dependence(d, reexports));
+        return this;
+      }
+
+      public Builder exports(String p, Set<String> ms) {
+        Objects.requireNonNull(p);
+        Objects.requireNonNull(ms);
+        if (!exports.containsKey(p))
+          exports.put(p, new HashSet<>());
+        exports.get(p).addAll(ms);
+        return this;
+      }
+
+      public Builder use(String cn) {
+        uses.add(cn);
+        return this;
+      }
+
+      public Builder provide(String s, String impl) {
+        provides.computeIfAbsent(s, _k -> new HashSet<>()).add(impl);
+        return this;
+      }
+
+      /**
+       * @return The new module
+       */
+      public Module build() {
+        return new Module(requires, exports, uses, provides);
+      }
+    }
+  }
+
+  // Helper class
+  private class Pair<F, S> {
+    public final F first;
+    public final S second;
+
+    Pair(F first, S second) {
+      this.first = first;
+      this.second = second;
+    }
+  }
+
+  /**
+   * u2 requires_count;
+   * { u2 requires_index;
+   * u2 requires_flags;
+   * } requires[requires_count];
+   */
+  class RequiresStruct implements Data {
+    List<Pair<ConstantPool.ConstCell, Integer>> list = new ArrayList<>();
+
+    public RequiresStruct() {
+      buildModuleIfAbsent().requires.forEach(
+          r -> list.add(
+              new Pair<>(clsData.pool.FindCellAsciz(r.name()),
+                  r.isReexports() ? ACC_REEXPORT : ACC_NONE))
+      );
+    }
+
+    @Override
+    public int getLength() { return 2 + list.size() * 4; }
+
+    @Override
+    public void write(CheckedDataOutputStream out) throws IOException {
+      out.writeShort(list.size());    // u2 requires_count;
+      for (Pair<ConstantPool.ConstCell, Integer> p : list) {
+        out.writeShort(p.first.arg);      // u2 requires_index;
+        out.writeShort(p.second);         // u2 requires_flags;
+      }
+    }
+  }
+
+  /**
+   * u2 exports_count;
+   * { u2 exports_index;
+   * u2 exports_to_count;
+   * u2 exports_to_index[exports_to_count];
+   * } exports[exports_count];
+   */
+  private class ExportsStruct implements Data {
+    final List<Pair<ConstantPool.ConstCell, List<ConstantPool.ConstCell>>> exports = new ArrayList<>();
+
+    ExportsStruct() {
+      Objects.requireNonNull(module);
+      //(un)qualified module exports
+      buildModuleIfAbsent().exports.entrySet().stream()
+          .sorted(Map.Entry.comparingByKey())
+          .forEach(e -> {
+                ArrayList<ConstantPool.ConstCell> to = new ArrayList<>();
+                e.getValue().forEach(mn -> to.add(clsData.pool.FindCellAsciz(mn)));
+                exports.add(new Pair<>(clsData.pool.FindCellAsciz(e.getKey()), to));
+              }
+          );
+    }
+
+    @Override
+    public int getLength() {
+      return 2 + 4 * exports.size() + exports.stream().mapToInt(p->p.second.size()).filter(s->s>0).sum() * 2;
+    }
+
+    @Override
+    public void write(CheckedDataOutputStream out) throws IOException {
+      out.writeShort(exports.size());         // u2 exports_count;
+      for (Pair<ConstantPool.ConstCell, List<ConstantPool.ConstCell>> pair : exports) {
+        out.writeShort(pair.first.arg);       // u2 exports_index;
+        out.writeShort(pair.second.size());   // u2 exports_to_count;
+        for( ConstantPool.ConstCell to : pair.second ) {
+            out.writeShort(to.arg);           // u2 exports_to_index[exports_to_count];
+        }
+      }
+    }
+  }
+
+  /**
+   * u2 uses_count;
+   * u2 uses_index[uses_count];
+   */
+  private class UsesStruct implements Data {
+    final List<ConstantPool.ConstCell> uses = new ArrayList<>();
+
+    UsesStruct() {
+      buildModuleIfAbsent().uses.stream().sorted().forEach(u -> uses.add(clsData.pool.FindCellAsciz(u)));
+    }
+
+    @Override
+    public int getLength() {
+      return 2 + 2 * uses.size();
+    }
+
+    @Override
+    public void write(CheckedDataOutputStream out) throws IOException {
+      out.writeShort(uses.size());
+      for (ConstantPool.ConstCell u : uses)
+        out.writeShort(u.arg);
+    }
+  }
+
+  /**
+   * u2 provides_count;
+   * { u2 provides_index;
+   * u2 with_index;
+   * } provides[provides_count];
+   */
+  private class ProvidesStruct implements Data {
+    List<Pair<ConstantPool.ConstCell, ConstantPool.ConstCell>> list = new ArrayList<>();
+
+    protected ProvidesStruct() {
+      buildModuleIfAbsent().provides.entrySet().stream()
+          .sorted(Map.Entry.comparingByKey())
+          .forEach(e -> e.getValue().stream()
+              .sorted()
+              .forEach(impl -> list.add(new Pair<>(
+                  clsData.pool.FindCellAsciz(e.getKey()), clsData.pool.FindCellAsciz(impl))
+              )));
+    }
+
+    @Override
+    public int getLength() {
+      return 2 + list.size() * 4;
+    }
+
+    @Override
+    public void write(CheckedDataOutputStream out) throws IOException {
+      out.writeShort(list.size());        // u2 provides_count;
+      for (Pair<ConstantPool.ConstCell, ConstantPool.ConstCell> p : list) {
+        out.writeShort(p.first.arg);      // u2 requires_index;
+        out.writeShort(p.second.arg);     // u2 requires_flags;
+      }
+    }
+  }
+}
--- a/src/org/openjdk/asmtools/jasm/Parser.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jasm/Parser.java	Wed Mar 02 14:00:55 2016 -0700
@@ -23,14 +23,16 @@
 
 package org.openjdk.asmtools.jasm;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import static org.openjdk.asmtools.jasm.ConstantPool.*;
 import static org.openjdk.asmtools.jasm.Constants.DEFAULT_MAJOR_VERSION;
 import static org.openjdk.asmtools.jasm.Constants.DEFAULT_MINOR_VERSION;
-import static org.openjdk.asmtools.jasm.ConstantPool.*;
+import static org.openjdk.asmtools.jasm.JasmTokens.Token;
 import static org.openjdk.asmtools.jasm.RuntimeConstants.*;
 import static org.openjdk.asmtools.jasm.Tables.*;
-import static org.openjdk.asmtools.jasm.JasmTokens.*;
-import java.io.IOException;
-import java.util.ArrayList;
 
 /**
  * This class is used to parse Jasm statements and expressions.
@@ -83,15 +85,15 @@
     private String                      pkg = null;
     private String                      pkgPrefix = "";
     private ArrayList<AnnotationData>   pkgAnnttns = null;
-    /* RemoveModules
-    private String mdl = null;
-    private ArrayList<AnnotationData> mdlAnnttns = null;
-     */
+    private String                      mdl = null;
+    private ArrayList<AnnotationData>   mdlAnnttns = null;
     private ArrayList<AnnotationData>   clsAnnttns = null;
     private ArrayList<AnnotationData>   memberAnnttns = null;
     private short                       major_version = DEFAULT_MAJOR_VERSION;
     private short                       minor_version = DEFAULT_MINOR_VERSION;
     private boolean                     explicitcp = false;
+    private ModuleAttr                  moduleAttribute;
+
 
     /** other parser components */
     private ParserAnnotation            annotParser = null;     // For parsing Annotations
@@ -132,8 +134,6 @@
     }
 
 
-
-
     /*---------------------------------------------*/
 
     protected String encodeClassString(String classname) {
@@ -571,7 +571,6 @@
                 case STRICT:       nextmod = ACC_STRICT;       break;
                 case ENUM:         nextmod = ACC_ENUM;         break;
                 case SYNTHETIC:    nextmod = ACC_SYNTHETIC;    break;
- //               case SYNTHETIC:    nextmod = SYNTHETIC_ATTRIBUTE;    break;
 
                 case DEPRECATED:   nextmod = DEPRECATED_ATTRIBUTE;   break;
                 case MANDATED:       nextmod = ACC_MANDATED;       break;
@@ -1214,7 +1213,278 @@
         endClass(scanner.prevPos);
     } // end parseClass
 
-    private void parseClassMembers() throws IOException {
+  /**
+   * Parses a module, package or type name in a module statement(s)
+   */
+    private String parseTypeName() throws IOException {
+        String name = "";
+        while (true) {
+            if ( scanner.token == Token.IDENT ) {
+                name += String.format("%s%s", name.isEmpty() ? "" : "/", scanner.idValue);
+                scanner.scan();
+            } else {
+                env.error(scanner.pos, "name.expected");
+                throw new Scanner.SyntaxError();
+            }
+            if(scanner.token == Token.FIELD) {
+                scanner.scan();
+            } else {
+                break;
+            }
+        }
+        return name;
+    }
+
+  /**
+   * Parse a module declaration.
+   */
+  protected void parseModule() throws IOException {
+    debugStr("   [Parser.parseModule]:  Begin ");
+
+    if (cd == null) {
+      cd = new ClassData(env, major_version, minor_version);
+      pool = cd.pool;
+    }
+
+    // move the tokenizer to the identifier:
+    if (scanner.token == Token.MODULE) {
+      scanner.scan();
+    }
+
+    // Parse the module name
+    String name = parseTypeName();
+
+    if (scanner.token == Token.SEMICOLON) {
+      // drop the semi-colon following a name
+      scanner.scan();
+    }
+    if (name.isEmpty()) {
+      env.error(scanner.pos, "name.expected");
+      throw new Scanner.SyntaxError();
+    }
+    ConstCell this_cpx = pool.FindCellClassByName(name + "/module-info");
+    moduleAttribute    = new ModuleAttr(cd);
+
+    parseVersion();
+    scanner.expect(Token.LBRACE);
+
+    // Begin a new class as module
+    cd.initAsModule(this_cpx);
+
+    // Parse module statement(s)
+    while ((scanner.token != Token.EOF) && (scanner.token != Token.RBRACE)) {
+      switch (scanner.token) {
+        case REQUIRES:
+          scanRequires(moduleAttribute);
+          break;
+        case EXPORTS:
+          scanExports(moduleAttribute);
+          break;
+        case USES:
+          scanUses(moduleAttribute);
+          break;
+        case PROVIDES:
+          scanProvidesWith(moduleAttribute);
+          break;
+        case SEMICOLON:
+          // Empty fields are allowed
+          scanner.scan();
+          break;
+        default:
+          env.error(scanner.pos, "module.statement.expected");
+          throw new Scanner.SyntaxError();
+      }  // end switch
+    } // while
+    scanner.expect(Token.RBRACE);
+    // End the module
+    endModule();
+  } // end parseModule
+
+  /**
+   * Scans  ModuleStatement: provides TypeName with TypeName ;
+   */
+  private void scanProvidesWith(ModuleAttr attr) throws IOException {
+    String s = "", impl = "";
+    boolean inWith = false;
+    scanner.scan();
+    while (scanner.token != Token.SEMICOLON) {
+      switch (scanner.token) {
+        case IDENT:
+          if (s.isEmpty() && !inWith) {
+            s = parseTypeName();
+            continue;
+          } else if (impl.isEmpty() && inWith) {
+            impl = parseTypeName();
+            continue;
+          }
+          env.error(scanner.pos, "provides.expected");
+          throw new Scanner.SyntaxError();
+        case WITH:
+          if (s.isEmpty() || inWith) {
+            env.error(scanner.pos, "provides.expected");
+            throw new Scanner.SyntaxError();
+          }
+          inWith = true;
+          break;
+        default:
+          env.error(scanner.pos, "provides.expected");
+          throw new Scanner.SyntaxError();
+      }
+      scanner.scan();
+    }
+    // Token.SEMICOLON
+    if (s.isEmpty() || impl.isEmpty()) {
+      env.error(scanner.pos, "provides.expected");
+      throw new Scanner.SyntaxError();
+    }
+    attr.provide(s, impl);
+    scanner.scan();
+  }
+
+  /**
+   * Scans  ModuleStatement: uses TypeName ;
+   */
+  private void scanUses(ModuleAttr attr) throws IOException {
+    String tn = "";
+    scanner.scan();
+    while (scanner.token != Token.SEMICOLON) {
+      switch (scanner.token) {
+        case IDENT:
+          if (!tn.isEmpty()) {
+            env.error(scanner.pos, "uses.expected");
+            throw new Scanner.SyntaxError();
+          }
+          tn = parseTypeName();
+          continue;
+        default:
+          env.error(scanner.pos, "uses.expected");
+          throw new Scanner.SyntaxError();
+      }
+    }
+    // Token.SEMICOLON
+    if (tn.isEmpty()) {
+      env.error(scanner.pos, "uses.expected");
+      throw new Scanner.SyntaxError();
+    }
+    attr.use(tn);
+    scanner.scan();
+  }
+
+  /**
+   * Scans  ModuleStatement: exports PackageName [to ModuleName {, ModuleName}] ;
+   */
+  private void scanExports(ModuleAttr attr) throws IOException {
+    String pn = "";
+    HashSet<String> mn = new HashSet<>();
+    scanner.scan();
+    while (scanner.token != Token.SEMICOLON) {
+      switch (scanner.token) {
+        case IDENT:
+          if (!pn.isEmpty()) {
+            env.error(scanner.pos, "exports.expected");
+            throw new Scanner.SyntaxError();
+          }
+          pn = parseTypeName();
+          continue;
+        case TO:
+          if (pn.isEmpty()) {
+            env.error(scanner.pos, "exports.expected");
+            throw new Scanner.SyntaxError();
+          }
+          mn = scanTo();
+          continue;
+        default:
+          env.error(scanner.pos, "exports.expected");
+          throw new Scanner.SyntaxError();
+      }
+    }
+    // Token.SEMICOLON
+    if (pn.isEmpty()) {
+      env.error(scanner.pos, "exports.expected");
+      throw new Scanner.SyntaxError();
+    }
+    attr.exports(pn, mn);
+    scanner.scan();
+  }
+
+  /**
+   * Scans the "to" part of ModuleStatement: exports PackageName [to ModuleName {, ModuleName}] ;
+   * : [to ModuleName {, ModuleName}] ;
+   */
+  private HashSet<String> scanTo() throws IOException {
+    HashSet<String> names = new HashSet<>();
+    boolean nextID = true;
+    scanner.scan();
+    while (scanner.token != Token.SEMICOLON) {
+      switch (scanner.token) {
+        case COMMA:
+          if (nextID) {
+            env.error(scanner.pos, "exports.expected");
+            throw new Scanner.SyntaxError();
+          }
+          nextID = true;
+          break;
+        case IDENT:
+          if (!nextID) {
+            env.error(scanner.pos, "exports.expected");
+            throw new Scanner.SyntaxError();
+          }
+          names.add(parseTypeName());
+          nextID = false;
+          continue;
+        default:
+          env.error(scanner.pos, "exports.expected");
+          throw new Scanner.SyntaxError();
+      }
+      scanner.scan();
+    }
+    // Token.SEMICOLON
+    if (names.isEmpty()) {
+      env.error(scanner.pos, "exports.expected");
+      throw new Scanner.SyntaxError();
+    }
+    return names;
+  }
+
+  /**
+   * Scans  ModuleStatement: requires [public] ModuleName ;
+   */
+  private void scanRequires(ModuleAttr attr) throws IOException {
+    boolean reexports = false;
+    String mn = "";
+    scanner.scan();
+    while (scanner.token != Token.SEMICOLON) {
+      switch (scanner.token) {
+        case IDENT:
+          if (!mn.isEmpty()) {
+            env.error(scanner.pos, "requires.expected");
+            throw new Scanner.SyntaxError();
+          }
+          mn = parseTypeName();
+          continue;
+          case PUBLIC:
+          if (reexports || !mn.isEmpty()) {
+            env.error(scanner.pos, "requires.expected");
+            throw new Scanner.SyntaxError();
+          }
+          reexports = true;
+          break;
+        default:
+          env.error(scanner.pos, "requires.expected");
+          throw new Scanner.SyntaxError();
+      }
+      scanner.scan();
+    }
+    // Token.SEMICOLON
+    if (mn.isEmpty()) {
+      env.error(scanner.pos, "requires.expected");
+      throw new Scanner.SyntaxError();
+    }
+    attr.require(mn, reexports);
+    scanner.scan();
+  }
+
+  private void parseClassMembers() throws IOException {
         debugScan("[Parser.parseClassMembers]:  Begin ");
         // Parse annotations
         if (scanner.token == Token.ANNOTATION) {
@@ -1307,6 +1577,15 @@
         cd = null;
     }
 
+    /**
+     * End module
+     */
+    protected void endModule() {
+        cd.endModule(moduleAttribute);
+        clsDataList.add(cd);
+        cd = null;
+    }
+
     public final ClassData[] getClassesData() {
         return ((ClassData[]) clsDataList.toArray(new ClassData[0]));
     }
@@ -1417,29 +1696,34 @@
                     int mod = scanModifiers();
                     if (mod == 0) {
                         switch (scanner.token) {
-                        case CLASS:
-                        case CPINDEX:
-                        case STRINGVAL:
-                        case IDENT:
-                          // this is a class declaration anyway
-                          break;
-                        case SEMICOLON:
-                          // Bogus semi colon
-                          scanner.scan();
-                          continue;
-                        default:
-                          // no class declaration found
-                          debugScan(" [Parser.parseFile]: ");
-                          env.error(scanner.pos, "toplevel.expected");
-                          throw new Scanner.SyntaxError();
+                            case MODULE:
+                            case CLASS:
+                            case CPINDEX:
+                            case STRINGVAL:
+                            case IDENT:
+                                // this is a class declaration anyway
+                                break;
+                            case SEMICOLON:
+                                // Bogus semi colon
+                                scanner.scan();
+                                continue;
+                            default:
+                                // no class declaration found
+                                debugScan(" [Parser.parseFile]: ");
+                                env.error(scanner.pos, "toplevel.expected");
+                                throw new Scanner.SyntaxError();
                         }
                     } else if (Modifiers.isInterface(mod) && (scanner.token != Token.CLASS)) {
                         // rare syntactic sugar:
                         // interface <ident> == abstract interface class <ident>
                         mod |= ACC_ABSTRACT;
                     }
-                    parseClass(mod);
+                    if( scanner.token == Token.MODULE )
+                        parseModule();
+                    else
+                        parseClass(mod);
                     clsAnnttns = null;
+
                 } catch (Scanner.SyntaxError e) {
                     // KTL
                     env.traceln("^^^^^^^ Syntax Error ^^^^^^^^^^^^");
--- a/src/org/openjdk/asmtools/jasm/ParserCP.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jasm/ParserCP.java	Wed Mar 02 14:00:55 2016 -0700
@@ -22,11 +22,12 @@
  */
 package org.openjdk.asmtools.jasm;
 
-import static org.openjdk.asmtools.jasm.JasmTokens.*;
-import static org.openjdk.asmtools.jasm.Tables.*;
 import java.io.IOException;
 import java.util.ArrayList;
 
+import static org.openjdk.asmtools.jasm.JasmTokens.Token;
+import static org.openjdk.asmtools.jasm.Tables.*;
+
 /**
  * ParserCP
  *
@@ -584,4 +585,5 @@
             return parser.pool.FindCell(ref);
         }
     } // end parseConstRef
+
 }
--- a/src/org/openjdk/asmtools/jasm/RuntimeConstants.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jasm/RuntimeConstants.java	Wed Mar 02 14:00:55 2016 -0700
@@ -65,12 +65,14 @@
     public static final int JAVA_MINOR_VERSION           = 3;
     /* Access Flags */
 
+    public static final int ACC_NONE          = 0x0000; // <<everywhere>>
     public static final int ACC_PUBLIC        = 0x0001; // class, inner, field, method
     public static final int ACC_PRIVATE       = 0x0002; //        inner, field, method
     public static final int ACC_PROTECTED     = 0x0004; //        inner, field, method
     public static final int ACC_STATIC        = 0x0008; //        inner, field, method
     public static final int ACC_FINAL         = 0x0010; // class, inner, field, method
     public static final int ACC_SUPER         = 0x0020; // class
+    public static final int ACC_REEXPORT      = 0x0020; //                             requires (ACC_PUBLIC)
     public static final int ACC_SYNCHRONIZED  = 0x0020; //                      method
     public static final int ACC_VOLATILE      = 0x0040; //               field
     public static final int ACC_BRIDGE        = 0x0040; //                      method
@@ -80,11 +82,11 @@
     public static final int ACC_INTERFACE     = 0x0200; // class, inner
     public static final int ACC_ABSTRACT      = 0x0400; // class, inner,        method
     public static final int ACC_STRICT        = 0x0800; //                      method
-    public static final int ACC_SYNTHETIC     = 0x1000; // class, inner, field, method
+    public static final int ACC_SYNTHETIC     = 0x1000; // class, inner, field, method requires
     public static final int ACC_ANNOTATION    = 0x2000; // class, inner
     public static final int ACC_ENUM          = 0x4000; // class, inner, field
-//    public static final int ACC_MODULE        = 0x8000; // class, inner, field, method
-    public static final int ACC_MANDATED      = 0x8000; //                      method
+    public static final int ACC_MODULE        = 0x8000; // class
+    public static final int ACC_MANDATED      = 0x8000; //                      method requires
 
     /* Attribute codes */
     public static final int SYNTHETIC_ATTRIBUTE          = 0x00010000; // actually, this is an attribute
--- a/src/org/openjdk/asmtools/jasm/Tables.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jasm/Tables.java	Wed Mar 02 14:00:55 2016 -0700
@@ -208,7 +208,8 @@
         ATT_BootstrapMethods                        (21, "ATT_BootstrapMethods", "BootstrapMethods"),
         ATT_RuntimeVisibleTypeAnnotations           (22, "ATT_RuntimeVisibleTypeAnnotations", "RuntimeVisibleTypeAnnotations"),
         ATT_RuntimeInvisibleTypeAnnotations         (23, "ATT_RuntimeInvisibleTypeAnnotations", "RuntimeInvisibleTypeAnnotations"),
-        ATT_MethodParameters                        (24, "ATT_MethodParameters", "MethodParameters");
+        ATT_MethodParameters                        (24, "ATT_MethodParameters", "MethodParameters"),
+        ATT_Module                                  (25, "ATT_Module", "Module");
 
         private final Integer value;
         private final String printval;
--- a/src/org/openjdk/asmtools/jasm/TypeAnnotationUtils.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jasm/TypeAnnotationUtils.java	Wed Mar 02 14:00:55 2016 -0700
@@ -150,7 +150,7 @@
         meth_type_param_bnds        (0x12, "METHOD_TYPE_PARAMETER_BOUND",  InfoType.TYPEPARAM_BOUND, "method type parameter bounds"),
         field                       (0x13, "FIELD",  InfoType.EMPTY, "field"),
         meth_ret_type               (0x14, "METHOD_RETURN",  InfoType.EMPTY, "method return type"),
-        meth_reciever               (0x15, "METHOD_RECEIVER",  InfoType.EMPTY, "method reciever"),
+        meth_receiver               (0x15, "METHOD_RECEIVER",  InfoType.EMPTY, "method receiver"),
         meth_formal_param           (0x16, "METHOD_FORMAL_PARAMETER",  InfoType.METHODPARAM, "method formal parameter type"),
         throws_type                 (0x17, "THROWS",  InfoType.EXCEPTION, "exception type in throws"),
         local_var                   (0x40, "LOCAL_VARIABLE",  InfoType.LOCALVAR, "local variable"),
@@ -799,7 +799,7 @@
                     break;
                 case field:
                 case meth_ret_type:
-                case meth_reciever:
+                case meth_receiver:
                     visit_empty_target(tt);
                     break;
                 case meth_formal_param:
--- a/src/org/openjdk/asmtools/jasm/i18n.properties	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jasm/i18n.properties	Wed Mar 02 14:00:55 2016 -0700
@@ -171,6 +171,13 @@
 err.incorrect.typeannot.targtype.int=Incorrect TypeAnnotation \"{0}\" argument: (expected Integer),  \"{1}\".
 err.incorrect.typeannot.pathentry=Incorrect TypeAnnotation TargetPath PathEntry \"{0}\".
 err.incorrect.typeannot.pathentry.argindex=Incorrect TypeAnnotation  TargetPath PathEntry ArgIndex (expected Integer),  \"{0}\".
+#
+# module Errors
+err.requires.expected=Module statement \"requires [public] ModuleName;\" expected.
+err.exports.expected=Module statement \"exports PackageName [to ModuleName (, ModuleName)];\" expected.
+err.uses.expected=Module statement \"uses TypeName ;\" expected.
+err.provides.expected=Module statement \"provides TypeName with TypeName ;\" expected.
+err.module.statement.expected=One of a module statements - \"requires ...\", \"exports ...\", \"uses ...\" or \"provides ... with ...\"  expected.
 
 #
 # Compiler Errors
--- a/src/org/openjdk/asmtools/jcoder/JcodTokens.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jcoder/JcodTokens.java	Wed Mar 02 14:00:55 2016 -0700
@@ -101,6 +101,7 @@
         DIV                 (72, "DIV",             "div",          TokenType.KEYWORDS),
         EQ                  (73, "EQ",              "eq",           TokenType.KEYWORDS),
         ASSIGN              (74, "ASSIGN",          "assign",       TokenType.KEYWORDS),
+        MODULE              (75, "MODULE",          "module",       TokenType.KEYWORDS, KeywordType.KEYWORD),
 
         COLON               (134, "COLON",        ":",    TokenType.PUNCTUATION),
         SEMICOLON           (135, "SEMICOLON",    ";",    TokenType.PUNCTUATION, KeywordType.KEYWORD),
--- a/src/org/openjdk/asmtools/jcoder/Jcoder.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jcoder/Jcoder.java	Wed Mar 02 14:00:55 2016 -0700
@@ -621,6 +621,8 @@
             case IDENT:
                 if (prev == Token.FILE) {
                     buf.myname = scanner.stringValue;
+                } else if( prev == Token.MODULE) {
+                    buf.myname = "module-info.class";
                 } else {
                     buf.myname = scanner.stringValue + ".class";
                 }
@@ -671,6 +673,7 @@
                 try {
                     switch (scanner.token) {
                         case CLASS:
+                        case MODULE:
                         case INTERFACE:
                         case FILE:
                             // Start of a class
--- a/src/org/openjdk/asmtools/jdec/ClassData.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jdec/ClassData.java	Wed Mar 02 14:00:55 2016 -0700
@@ -22,14 +22,13 @@
  */
 package org.openjdk.asmtools.jdec;
 
+import org.openjdk.asmtools.jasm.Modifiers;
 import org.openjdk.asmtools.util.I18NResourceBundle;
+
+import java.io.*;
+
 import static org.openjdk.asmtools.jasm.Tables.*;
 import static org.openjdk.asmtools.jasm.TypeAnnotationUtils.*;
-import java.io.DataInputStream;
-import java.io.EOFException;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
 
 /**
  *
@@ -46,11 +45,14 @@
     String inpname;
     int[] cpe_pos;
     boolean printDetails;
+    String entityname;
+
 
     public static I18NResourceBundle i18n
             = I18NResourceBundle.getBundleForClass(Main.class);
 
     public ClassData(String inpname, int printFlags, PrintWriter out) throws IOException {
+        entityname = (inpname.endsWith("module-info.class")) ? "module" : "class";
         FileInputStream filein = new FileInputStream(inpname);
         byte buf[] = new byte[filein.available()];
         filein.read(buf);
@@ -219,7 +221,6 @@
                 case CONSTANT_FIELD:
                 case CONSTANT_METHOD:
                 case CONSTANT_NAMEANDTYPE:
-//                case CONSTANT_INVOKEDYNAMIC_TRANS:
                     cpool[i] = "#" + in.readUnsignedShort() + " #" + in.readUnsignedShort();
                     break;
                 case CONSTANT_INVOKEDYNAMIC:
@@ -427,13 +428,14 @@
     }
 
     /**
-     * Processes JSR-308 <code>extended_annotation</code> structure.
+     * Processes 4.7.20 The RuntimeVisibleTypeAnnotations Attribute, 4.7.21 The RuntimeInvisibleTypeAnnotations Attribute
+     * <code>type_annotation</code> structure.
      */
-    void decodeTargetTypeAndRefInfo(DataInputStream in, int len, PrintWriter out, boolean isWildcard) throws IOException {
-        int tt = in.readUnsignedShort();
+    void decodeTargetTypeAndRefInfo(DataInputStream in, PrintWriter out, boolean isWildcard) throws IOException {
+        int tt = in.readUnsignedByte(); // [4.7.20] annotations[], type_annotation { u1 target_type; ...}
         TargetType target_type = targetTypeEnum(tt);
         InfoType info_type = target_type.infoType();
-        out_println(toHex(tt, 2) + ";  //  target_type: " + target_type.parseKey());
+        out_println(toHex(tt, 1) + ";  //  target_type: " + target_type.parseKey());
 
         switch (info_type) {
             case TYPEPARAM:          //[3.3.1] meth_type_param, class_type_param:
@@ -446,7 +448,7 @@
                 out_println(toHex(in.readUnsignedByte(), 1) + ";  //  param_index");
                 out_println(toHex(in.readUnsignedByte(), 1) + ";  //  bound_index");
                 break;
-            case EMPTY:             //[3.3.4]  meth_reciever, meth_ret_type, field
+            case EMPTY:             //[3.3.4]  meth_receiver, meth_ret_type, field
                 // NOTE: reference_info is empty for this annotation's target
                 break;
             case METHODPARAM:       //[3.3.5]  meth_formal_param:
@@ -483,13 +485,14 @@
                 out_println(toHex(tt, 1) + "; // invalid target_info: " + tt);
                 throw new ClassFormatError();
         }
-        int path_len = in.readUnsignedShort();
-        startArrayCmt(path_len, "type_paths");
-        for (int i = 0; i < path_len; i++) {
-            // print the type Path elements
-            out_println("{ " + toHex(in.readUnsignedByte(), 1)
-                    + "; " + toHex(in.readUnsignedByte(), 1)
-                    + "; } // type_path[" + i + "]");  // type path kind
+        // [4.7.20.2]
+        int path_length = in.readUnsignedByte();  // type_path { u1 path_length; ...}
+        startArrayCmt(path_length, "type_paths");
+        for (int i = 0; i < path_length; i++) {
+            // print the type_path elements
+            out_println("{ " + toHex(in.readUnsignedByte(), 1)  // { u1 type_path_kind;
+                    + "; " + toHex(in.readUnsignedByte(), 1)    //   u1 type_argument_index; }
+                    + "; } // type_path[" + i + "]");           // path[i]
         }
         out_end("}");
 
@@ -560,8 +563,9 @@
         out_end("}  //  annotation");
     }
 
-    void decodeExtendedAnnotation(DataInputStream in, PrintWriter out) throws IOException {
-        out_begin("{  //  annotation");
+    void decodeTypeAnnotation(DataInputStream in, PrintWriter out) throws IOException {
+        out_begin("{  //  type_annotation");
+        decodeTargetTypeAndRefInfo(in, out, false);
         decodeCPXAttr(in, 2, "field descriptor", out);
         int evp_num = in.readUnsignedShort();
         startArrayCmt(evp_num, "element_value_pairs");
@@ -575,8 +579,7 @@
             }
         }
         out_end("}  //  element_value_pairs");
-        decodeTargetTypeAndRefInfo(in, 0, out, false);
-        out_end("}  //  annotation");
+        out_end("}  //  type_annotation");
     }
 
     void decodeBootstrapMethod(DataInputStream in, PrintWriter out) throws IOException {
@@ -778,12 +781,14 @@
                     }
                     out_end("}");
                     break;
+                // 4.7.20 The RuntimeVisibleTypeAnnotations Attribute
+                // 4.7.21 The RuntimeInvisibleTypeAnnotations Attribute
                 case ATT_RuntimeInvisibleTypeAnnotations:
                 case ATT_RuntimeVisibleTypeAnnotations:
                     int ant_num = in.readUnsignedShort();
                     startArrayCmt(ant_num, "annotations");
                     for (int i = 0; i < ant_num; i++) {
-                        decodeExtendedAnnotation(in, out);
+                        decodeTypeAnnotation(in, out);
                         if (i < ant_num - 1) {
                             out_println(";");
                         }
@@ -821,6 +826,9 @@
                     }
                     out_end("}");
                     break;
+                case ATT_Module:
+                    decodeModule(in, out);
+                    break;
                 default:
                     if (AttrName == null) {
                         printBytes(out, in, len);
@@ -843,6 +851,52 @@
         countedin.leave();
     }
 
+    void decodeModule(DataInputStream in, PrintWriter out) throws IOException {
+        int count = in.readUnsignedShort(); // u2 requires_count
+        startArrayCmt(count, "requires");
+        for (int i = 0; i < count; i++) {
+            // u2 requires_index; u2 requires_flag
+            out_println("#" + in.readUnsignedShort() + " " + toHex(in.readUnsignedShort(), 2) + ";");
+        }
+        out_end("} // requires\n");
+
+//        count = in.readUnsignedShort();     // u2 permits_count
+//        startArrayCmt(count, "permits");
+//        for (int i = 0; i < count; i++) {
+//            // u2 permits_index
+//            out_println("#" + in.readUnsignedShort() + ";");
+//        }
+//        out_end("} // permits\n");
+
+        count = in.readUnsignedShort();     // u2 exports_count
+        startArrayCmt(count, "exports");
+        for (int i = 0; i < count; i++) {
+            // u2 exports_index
+            out_println("#" + in.readUnsignedShort());
+            int exports_to_count = in.readUnsignedShort();
+            startArrayCmt(exports_to_count, "to");
+            for (int j = 0; j < exports_to_count; j++) {
+                out_println("#" + in.readUnsignedShort() + ";");
+            }
+            out_end("}; // end to");
+        }
+        out_end("} // exports\n");
+        count = in.readUnsignedShort();     // u2 uses_count
+        startArrayCmt(count, "uses");
+        for (int i = 0; i < count; i++) {
+            // u2 uses_index
+            out_println("#" + in.readUnsignedShort() + ";");
+        }
+        out_end("} // uses\n");
+        count = in.readUnsignedShort(); // u2 provides_count
+        startArrayCmt(count, "provides");
+        for (int i = 0; i < count; i++) {
+            // u2 provides_index; u2 with_index
+            out_println("#" + in.readUnsignedShort() + " #" + in.readUnsignedShort() + ";");
+        }
+        out_end("} // provides\n");
+    }
+
     void decodeAttrs(DataInputStream in, PrintWriter out) throws IOException {
         // Read the attributes
         int attr_num = in.readUnsignedShort();
@@ -883,7 +937,7 @@
     }
 
     public void decodeClass() throws IOException {
-        String classname = "N/A";
+        String classname  = "N/A";
         // Read the header
         try {
             int magic = in.readInt();
@@ -892,16 +946,21 @@
 
             // Read the constant pool
             readCP(in);
-            short access = in.readShort(); // dont care about sign
+            short access = in.readShort(); // don't care about sign
             int this_cpx = in.readUnsignedShort();
 
             try {
                 classname = (String) cpool[((Integer) cpool[this_cpx]).intValue()];
-                out_begin("class " + classname + " {");
+                int ind = classname.lastIndexOf("module-info");
+                if( ind > -1) {
+                    entityname = "module";
+                    classname = classname.substring(0, --ind < 0 ? 0 : ind ).replace('/', '.');
+                }
+                out_begin(String.format("%s %s {", entityname, classname));
             } catch (Exception e) {
                 classname = inpname;
                 out.println("// " + e.getMessage() + " while accessing classname");
-                out_begin("class " + classname + " { // source file name");
+                out_begin(String.format("%s %s { // source file name", entityname, classname));
             }
 
             out_print(toHex(magic, 4) + ";");
@@ -914,8 +973,8 @@
 
             // Print the constant pool
             printCP(out);
-
-            out_println(toHex(access, 2) + "; // access");
+            out_println(toHex(access, 2) + "; // access"  +
+            ( printDetails ? " [" +  (" " + Modifiers.accessString(access, CF_Context.CTX_CLASS).toUpperCase()).replaceAll(" (\\S)"," ACC_$1") + "]" : "" ));
             out_println("#" + this_cpx + ";// this_cpx");
             int super_cpx = in.readUnsignedShort();
             out_println("#" + super_cpx + ";// super_cpx");
@@ -946,7 +1005,7 @@
             out.println("//------- ClassFormatError:" + err.getMessage());
             printRestOfBytes();
         } finally {
-            out_end("} // end class " + classname);
+            out_end(String.format("} // end %s %s", entityname, classname));
         }
     } // end decodeClass()
     /* ====================================================== */
--- a/src/org/openjdk/asmtools/jdis/ClassData.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jdis/ClassData.java	Wed Mar 02 14:00:55 2016 -0700
@@ -24,10 +24,12 @@
 
 import org.openjdk.asmtools.asmutils.HexUtils;
 import org.openjdk.asmtools.jasm.Modifiers;
+
+import java.io.*;
+import java.util.ArrayList;
+
 import static org.openjdk.asmtools.jasm.RuntimeConstants.*;
 import static org.openjdk.asmtools.jasm.Tables.*;
-import java.io.*;
-import java.util.ArrayList;
 
 /**
  * Central class data for of the Java Disassembler
@@ -88,6 +90,11 @@
      */
     protected ArrayList<BootstrapMethodData> bootstrapMethods;
 
+    /**
+     * The module this class file presents
+     */
+    protected ModuleData moduleData;
+
     // other parsing fields
     protected PrintWriter out;
     protected String pkgPrefix = "";
@@ -105,7 +112,6 @@
         memberType = "ClassData";
         TraceUtils.traceln("printOptions=" + options.toString());
         pool = new ConstantPool(this);
-
     }
 
     /*========================================================*/
@@ -217,6 +223,11 @@
                     bootstrapMethods.add(bsmData);
                 }
                 break;
+            case ATT_Module:
+                // Read Module Attribute
+                moduleData = new ModuleData(this);
+                moduleData.read(in);
+                break;
             default:
                 handled = false;
                 break;
@@ -295,11 +306,13 @@
 
     public void print() throws IOException {
         int k, l;
-// Write the header
-        String classname = pool.getClassName(this_cpx);
-        pkgPrefixLen = classname.lastIndexOf("/") + 1;
+        String className = pool.getClassName(this_cpx);
+        String moduleName = pool.getModuleName(this_cpx);
+        boolean isModuleUnit = !moduleName.isEmpty();
+        pkgPrefixLen = className.lastIndexOf("/") + 1;
+        // Write the header
         // package-info compilation unit
-        if (classname.endsWith("package-info")) {
+        if (className.endsWith("package-info")) {
             // Print the Annotations
             if (visibleAnnotations != null) {
                 for (AnnotationData visad : visibleAnnotations) {
@@ -329,53 +342,53 @@
             }
 
             if (pkgPrefixLen != 0) {
-                pkgPrefix = classname.substring(0, pkgPrefixLen);
+                pkgPrefix = className.substring(0, pkgPrefixLen);
                 out.print("package  " + pkgPrefix.substring(0, pkgPrefixLen - 1) + " ");
                 out.print("version " + major_version + ":" + minor_version + ";");
             }
             out.println();
             return;
         }
+        if (!isModuleUnit) {
+            if (pkgPrefixLen != 0) {
+                pkgPrefix = className.substring(0, pkgPrefixLen);
+                out.println("package  " + pkgPrefix.substring(0, pkgPrefixLen - 1) + ";");
+                className = pool.getShortClassName(this_cpx, pkgPrefix);
+            }
+            out.println();
 
-        if (pkgPrefixLen != 0) {
-            pkgPrefix = classname.substring(0, pkgPrefixLen);
-            out.println("package  " + pkgPrefix.substring(0, pkgPrefixLen - 1) + ";");
-            classname = pool.getShortClassName(this_cpx, pkgPrefix);
-        }
-        out.println();
-
-        // Print the Annotations
-        if (visibleAnnotations != null) {
-            for (AnnotationData visad : visibleAnnotations) {
-                visad.print(out, initialTab);
+            // Print the Annotations
+            if (visibleAnnotations != null) {
+                for (AnnotationData visad : visibleAnnotations) {
+                    visad.print(out, initialTab);
+                    out.println();
+                }
+            }
+            if (invisibleAnnotations != null) {
+                for (AnnotationData invisad : invisibleAnnotations) {
+                    invisad.print(out, initialTab);
+                    out.println();
+                }
+            }
+            if (visibleTypeAnnotations != null) {
                 out.println();
+                for (TypeAnnotationData visad : visibleTypeAnnotations) {
+                    visad.print(out, initialTab);
+                    out.println();
+                }
             }
-        }
-        if (invisibleAnnotations != null) {
-            for (AnnotationData invisad : invisibleAnnotations) {
-                invisad.print(out, initialTab);
+            if (invisibleTypeAnnotations != null) {
                 out.println();
+                for (TypeAnnotationData invisad : invisibleTypeAnnotations) {
+                    invisad.print(out, initialTab);
+                    out.println();
+                }
             }
-        }
-        if (visibleTypeAnnotations != null) {
-            out.println();
-            for (TypeAnnotationData visad : visibleTypeAnnotations) {
-                visad.print(out, initialTab);
-                out.println();
+            if ((access & ACC_SUPER) != 0) {
+                out.print("super ");
+                access = access & ~ACC_SUPER;
             }
         }
-        if (invisibleTypeAnnotations != null) {
-            out.println();
-            for (TypeAnnotationData invisad : invisibleTypeAnnotations) {
-                invisad.print(out, initialTab);
-                out.println();
-            }
-        }
-
-        if ((access & ACC_SUPER) != 0) {
-            out.print("super ");
-            access = access & ~ACC_SUPER;
-        }
 
         // see if we are going to print: abstract interface class
 // then replace it with just: interface
@@ -428,11 +441,12 @@
             pool.PrintConstant(out, this_cpx);
         }
         out.println();
-
-        if (!pool.getClassName(super_cpx).equals("java/lang/Object")) {
-            out.print("\textends ");
-            pool.printlnClassId(out, super_cpx);
-            out.println();
+        if(!isModuleUnit ) {
+            if (!pool.getClassName(super_cpx).equals("java/lang/Object")) {
+                out.print("\textends ");
+                pool.printlnClassId(out, super_cpx);
+                out.println();
+            }
         }
         l = interfaces.length;
 
@@ -459,7 +473,9 @@
             } catch (IOException e) {
             }
         }
-        out.println();
+        // keep this new line for classes to pass huge test suite.
+        if(!isModuleUnit)
+            out.println();
 
         // Print the constant pool
         if (options.contains(Options.PR.CP)) {
@@ -500,8 +516,16 @@
             }
             out.println();
         }
+        // Print module attributes
+        if( isModuleUnit  && moduleData != null) {
+            moduleData.print();
+        }
 
-        out.println("} // end Class " + classname);
+        if( isModuleUnit ) {
+            out.println("} // end Module " + moduleName);
+        } else {
+            out.println("} // end Class " + className);
+        }
 
     } // end ClassData.print()
 
--- a/src/org/openjdk/asmtools/jdis/ConstantPool.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jdis/ConstantPool.java	Wed Mar 02 14:00:55 2016 -0700
@@ -23,6 +23,7 @@
 package org.openjdk.asmtools.jdis;
 
 import org.openjdk.asmtools.asmutils.HexUtils;
+
 import java.io.DataInputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -772,6 +773,38 @@
 
     /**
      *
+     * getModuleName
+     *
+     * Gets a Java module name from a ConstantClass from the CP at a given index
+     * where this_class is presented in the form: [Module's name in internal form (JVMS 4.2.1)]/module-info
+     * Traditionally, if this_class indicates P/Q/R, then the ClassFile occupies a file R.class in a directory representing
+     * the package P.Q. Similarly, if this_class indicates P/Q/module-info, then the ClassFile occupies a file module-info.class
+     * in a directory representing the module P.Q.
+     *
+     * Returns the Java module name, or an empty string in any other case.
+     */
+    public String getModuleName(int cpx) {
+        String this_class = getClassName(cpx);
+        if(this_class.equals("#" + cpx)) {
+            return "";
+        }
+        return _getModuleName(this_class);
+    }
+
+  /**
+   * _getModuleName
+   *
+   * Helper for getting Module's name from the this_class string.
+   *
+   * Returns an empty string if this_class is not [Module's name in internal form (JVMS 4.2.1)]/module-info
+   */
+    private String _getModuleName(String this_class) {
+        int i = this_class.lastIndexOf("/module-info");
+        return (i>0) ? this_class.substring(0,i).replace('/', '.') : "";
+    }
+
+    /**
+     *
      * getClassName
      *
      * Safely gets a Java class name from a ConstantClass from a CPX2 constant pool
@@ -929,6 +962,7 @@
             return "#" + cpx;
         }
         switch (cns.tag) {
+
             case CONSTANT_METHODHANDLE:
             case CONSTANT_INVOKEDYNAMIC:
 //            case CONSTANT_INVOKEDYNAMIC_TRANS:
@@ -941,6 +975,12 @@
                 }
             }
         }
+        if(cns.tag == TAG.CONSTANT_CLASS) {
+            String moduleName = _getModuleName(StringValue(cpx));
+            if( !moduleName.isEmpty() ) {
+                return " " + moduleName;
+            }
+        }
         return cns.tag.tagname + " " + StringValue(cpx);
     }
 
--- a/src/org/openjdk/asmtools/jdis/MethodData.java	Fri Dec 19 11:29:43 2014 -0700
+++ b/src/org/openjdk/asmtools/jdis/MethodData.java	Wed Mar 02 14:00:55 2016 -0700
@@ -23,14 +23,16 @@
 package org.openjdk.asmtools.jdis;
 
 import org.openjdk.asmtools.jasm.Modifiers;
-import static org.openjdk.asmtools.jasm.Tables.*;
-import static org.openjdk.asmtools.jasm.JasmTokens.Token;
+
 import java.io.DataInputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 
+import static org.openjdk.asmtools.jasm.JasmTokens.Token;
+import static org.openjdk.asmtools.jasm.Tables.AttrTag;
+import static org.openjdk.asmtools.jasm.Tables.CF_Context;
+
 /**
- *
  * Method data for method members in a class of the Java Disassembler
  */
 public class MethodData extends MemberData {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/openjdk/asmtools/jdis/ModuleData.java	Wed Mar 02 14:00:55 2016 -0700
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2016, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.asmtools.jdis;
+
+import org.openjdk.asmtools.jasm.Modifiers;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+
+/**
+ *  The module attribute data.
+ */
+public class ModuleData {
+
+  // internal references
+  private ConstantPool pool;
+  private PrintWriter out;
+  private Module module;
+
+  public ModuleData(ClassData clsData) {
+    this.pool = clsData.pool;
+    this.out = clsData.out;
+  }
+
+  /**
+   * Reads and resolve the method's attribute data called from ClassData.
+   */
+  public void read(DataInputStream in) throws IOException {
+    Module.Builder builder = new Module.Builder();
+    int requires_count = in.readUnsignedShort();
+    for (int i = 0; i < requires_count; i++) {
+      int index = in.readUnsignedShort();
+      int flags = in.readUnsignedShort();
+      String moduleName = pool.getString(index);
+
+      Set<Modifier> mods;
+      if (flags == 0) {
+        mods = Collections.emptySet();
+      } else {
+        mods = new HashSet<>();
+        if (Modifiers.isReexport(flags))
+          mods.add(Modifier.PUBLIC);
+        if (Modifiers.isSynthetic(flags))
+          mods.add(Modifier.SYNTHETIC);
+        if (Modifiers.isMandated(flags))
+          mods.add(Modifier.MANDATED);
+      }
+      builder.require(moduleName, mods);
+    }
+
+    int exports_count = in.readUnsignedShort();
+    if (exports_count > 0) {
+      for (int i = 0; i < exports_count; i++) {
+        int index = in.readUnsignedShort();
+        String packageName = pool.getString(index).replace('/', '.');
+        int exports_to_count = in.readUnsignedShort();
+        if (exports_to_count > 0) {
+          Set<String> targets = new HashSet<>(exports_to_count);
+          for (int j = 0; j < exports_to_count; j++) {
+            int exports_to_index = in.readUnsignedShort();
+            targets.add(pool.getString(exports_to_index));
+          }
+          builder.exports(packageName, targets);
+        } else {
+          builder.export(packageName);
+        }
+      }
+    }
+
+    int uses_count = in.readUnsignedShort();
+    if (uses_count > 0) {
+      for (int i = 0; i < uses_count; i++) {
+        int index = in.readUnsignedShort();
+        String serviceName = pool.getClassName(index).replace('/', '.');
+        builder.use(serviceName);
+      }
+    }
+
+    int provides_count = in.readUnsignedShort();
+    if (provides_count > 0) {
+      Map<String, Set<String>> pm = new HashMap<>();
+      for (int i = 0; i < provides_count; i++) {
+        int index = in.readUnsignedShort();
+        int with_index = in.readUnsignedShort();
+        String sn = pool.getClassName(index).replace('/', '.');
+        String cn = pool.getClassName(with_index).replace('/', '.');
+        // computeIfAbsent
+        Set<String> providers = pm.get(sn);
+        if (providers == null) {
+          providers = new HashSet<>();
+          pm.put(sn, providers);
+        }
+        providers.add(cn);
+      }
+      for (Map.Entry<String, Set<String>> e : pm.entrySet()) {
+        builder.provide(e.getKey(), e.getValue());
+      }
+    }
+    module = builder.build();
+  }
+
+  /* Print Methods */
+  public void print() throws IOException {
+    if (module != null)
+      out.println(module.toString());
+  }
+
+
+  private enum Modifier {
+    PUBLIC(0x0020), SYNTHETIC(0x1000), MANDATED(0x8000);
+    private final int value;
+    Modifier(int value) {
+      this.value = value;
+    }
+  }
+
+  private final static class Module {
+    //* A service dependence's of this module
+    final Set<String> uses;
+    //* A module on which the current module has a dependence.
+    private final Set<Dependence> requires;
+    //* A module export, may be qualified or unqualified.
+    private final Map<String, Set<String>> exports;
+    //* A service that a module provides one or more implementations of.
+    private final Map<String, Set<String>> provides;
+
+    private Module(Set<Dependence> requires,
+                   Map<String, Set<String>> exports,
+                   Set<String> uses,
+                   Map<String, Set<String>> provides) {
+      this.requires = Collections.unmodifiableSet(requires);
+      this.exports = Collections.unmodifiableMap(exports);
+      this.uses = Collections.unmodifiableSet(uses);
+      this.provides = Collections.unmodifiableMap(provides);
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder();
+      requires.stream()
+          .sorted()
+          .forEach(d -> sb.append(format("  %s%n", d.toString())));
+      //
+      exports.entrySet().stream()
+          .filter(e -> e.getValue().isEmpty())
+          .sorted(Map.Entry.comparingByKey())
+          .map(e -> format("  exports %s;%n", e.getKey()))
+          .forEach(sb::append);
+      exports.entrySet().stream()
+          .filter(e -> !e.getValue().isEmpty())
+          .sorted(Map.Entry.comparingByKey())
+          .map(e -> format("  exports %s to%n%s;%n", e.getKey(),
+              e.getValue().stream().sorted()
+                  .map(mn -> format("      %s", mn))
+                  .collect(Collectors.joining(",\n"))))
+          .forEach(sb::append);
+      //
+      uses.stream().sorted()
+          .map(s -> format("  uses %s;%n", s))
+          .forEach(sb::append);
+      //
+      provides.entrySet().stream()
+          .sorted(Map.Entry.comparingByKey())
+          .flatMap(e -> e.getValue().stream().sorted()
+              .map(impl -> format("  provides %s with %s;%n", e.getKey(), impl)))
+          .forEach(sb::append);
+      return sb.toString();
+    }
+
+    //* A module on which the current module has a dependence.
+    private final static class Dependence implements Comparable<Dependence> {
+      private final String name;
+      private final Set<Modifier> flags;
+
+      public Dependence(String name, Set<Modifier> flags) {
+        this.name = name;
+        this.flags = flags;
+      }
+
+      /**
+       * Returns the module name.
+       *
+       * @return The module name
+       */
+      public String name() {
+        return name;
+      }
+
+      /**
+       * Returns the set of modifiers.
+       *
+       * @return A possibly-empty unmodifiable set of modifiers
+       */
+      public Set<Modifier> modifiers() {
+        return flags;
+      }
+
+      @Override
+      public int hashCode() {
+        return name.hashCode() * 11 + flags.hashCode();
+      }
+
+      @Override
+      public boolean equals(Object o) {
+        if (o instanceof Dependence) {
+          Dependence d = (Dependence) o;
+          return this.name.equals(d.name) && flags.equals(d.flags);
+        }
+        return false;
+      }
+
+      @Override
+      public int compareTo(Dependence o) {
+        int rc = this.name.compareTo(o.name);
+        return rc != 0 ? rc : Integer.compare(this.flagsValue(), o.flagsValue());
+      }
+
+      @Override
+      public String toString() {
+        return format("requires %s%s;", flags.contains(Modifier.PUBLIC) ? "public " : "", name);
+      }
+
+      private int flagsValue() {
+        int value = 0;
+        for (Modifier m : flags) {
+          value |= m.value;
+        }
+        return value;
+      }
+    }
+
+    /**
+     * The module builder.
+     */
+    public static final class Builder {
+
+      public final Set<Dependence> requires = new HashSet<>();
+      final Map<String, Set<String>> exports = new HashMap<>();
+      final Set<String> uses = new HashSet<>();
+      final Map<String, Set<String>> provides = new HashMap<>();
+
+      public Builder() {
+      }
+
+      /**
+       * Adds a module on which the current module has a dependence.
+       */
+      public Builder require(String d, Set<Modifier> flags) {
+        requires.add(new Dependence(d, flags));
+        return this;
+      }
+
+      /**
+       * Adds a unqualified module export.
+       */
+      public Builder export(String p) {
+        Objects.requireNonNull(p);
+        if (!exports.containsKey(p)) exports.put(p, new HashSet<>());
+        return this;
+      }
+
+      /**
+       * Adds a qualified module exports.
+       */
+      public Builder exports(String p, Set<String> ms) {
+        Objects.requireNonNull(p);
+        Objects.requireNonNull(ms);
+        if (!exports.containsKey(p)) export(p);
+        exports.get(p).addAll(ms);
+        return this;
+      }
+
+
+      /**
+       * Adds a service dependence's of this module
+       */
+      public Builder use(String cn) {
+        uses.add(cn);
+        return this;
+      }
+
+      /**
+       * Adds a service that a module provides one or more implementations of.
+       */
+      public Builder provide(String s, Set<String> impl) {
+        provides.computeIfAbsent(s, _k -> new HashSet<>()).addAll(impl);
+        return this;
+      }
+
+      /**
+       * @return The new module
+       */
+      public Module build() {
+        return new Module(requires, exports, uses, provides);
+      }
+    }
+  }
+}