1 /*
   2  * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package sun.module.tools;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.module.Module;
  31 import java.module.ModuleArchiveInfo;
  32 import java.module.ModuleDefinition;
  33 import java.module.ModuleInitializationException;
  34 import java.module.Modules;
  35 import java.module.Query;
  36 import java.module.Repository;
  37 import java.module.VersionConstraint;
  38 import java.util.LinkedHashSet;
  39 import java.util.Iterator;
  40 import java.util.List;
  41 import java.util.Set;
  42 import sun.module.JamUtils;
  43 
  44 /**
  45  * Provides a means to visit each of a module's imports.
  46  * @since 1.7
  47  */
  48 public class ImportTraverser implements Iterable<Module> {
  49     private Set<Module> visited = new LinkedHashSet<Module>();
  50 
  51     /**
  52      * Invokes a visitor on each of the imports of the module identified by
  53      * the parameters.   The imports are visited in depth-first order with respect to
  54      * the graph of interconnected modules.  Note that the module identified
  55      * by parameters is the first one visited.
  56      *
  57      * @param repo repository in which to find a named module
  58      * @param name name of the module to find
  59      * @param constraint version constraint on the module; may be null
  60      * @throws ModuleInitializationException if the identified module cannot be
  61      * instantiated
  62      */
  63     public void traverse(Visitor visitor, Repository repo,
  64                          String name,
  65                          VersionConstraint constraint)
  66             throws ModuleInitializationException{
  67 
  68         traverse(visitor, repo, name, constraint, null, null);
  69     }
  70 
  71     /**
  72      * Invokes a visitor on each of the imports of the module identified by
  73      * the parameters.   The imports are visited in depth-first order with respect to
  74      * the graph of interconnected modules.  Note that the module identified
  75      * by parameters is the first one visited.
  76      *
  77      * @param jamName name of a JAM file for which dependencies are returned
  78      * @throws java.io.IOException if a Repository for the named JAM file cannot
  79      * be created
  80      * @throws ModuleInitializationException if the identified module cannot be
  81      * instantiated
  82      */
  83 /*    public void traverse(Visitor, visitor, String jamName)
  84             throws IOException, ModuleInitializationException {
  85 
  86         File repoDir = JamUtils.createTempDir();
  87         Repository repo = Modules.newLocalRepository("importvisitor", repoDir);
  88         ModuleArchiveInfo mai = repo.install(new File(jamName).toURI().toURL());
  89         
  90         visit(visitor, repo, mai.getName(), mai.getVersion().toVersionConstraint(),
  91               mai.getPlatform(), mai.getArch());
  92     }
  93 */
  94     /**
  95      * Invokes a visitor on each of the imports of the module identified by
  96      * the parameters.   The imports are visited in depth-first order with respect to
  97      * the graph of interconnected modules.  Note that the module identified
  98      * by parameters is the first one visited.
  99      *
 100      * @param repo repository in which to find a named module
 101      * @param name name of the module to find
 102      * @param constraint version constraint on the module; may be null
 103      * @param platform platform of the module to find; may be null
 104      * @param arch architecture of the module to find; may be null
 105      * @throws ModuleInitializationException if the identified module cannot be
 106      * instantiated
 107      * @throws IllegalArgumentException if one of {@code platform, arch} is
 108      * null and the other is not, or if the {@code repository} finds more than
 109      * one module matching the given parameters.
 110      */
 111     public void traverse(Visitor visitor, Repository repo,
 112                          String name, VersionConstraint constraint,
 113                          String platform, String arch)
 114             throws ModuleInitializationException {
 115 
 116         if (repo == null || name == null) {
 117             throw new NullPointerException();
 118         }
 119 
 120         if (platform == null ^ arch == null) {
 121             throw new IllegalArgumentException(
 122                 "module platform and arch must be either both null or both non-null");
 123         }
 124         
 125         Query q = Query.module(
 126             name, constraint == null ? VersionConstraint.DEFAULT : constraint);
 127         if (platform != null) {
 128             q = Query.and(
 129                 q, Query.annotation(java.module.annotation.PlatformBinding.class));
 130         }
 131 
 132         List<ModuleDefinition> mdList = repo.find(q);
 133         if (mdList.size() > 1) {
 134             throw new IllegalArgumentException(
 135                 "more than one matching module found for name=" + name
 136                 + " version=" + constraint
 137                 + " platform=" + platform + " arch=" + arch);
 138         } else if (mdList.size() == 1) {
 139             Module m = mdList.get(0).getModuleInstance();
 140             visitor.init(m);
 141             traverse(visitor, m);
 142         }
 143     }
 144 
 145     /**
 146      * Invokes a visitor on each of the imports of the {@code module} The
 147      * imports are visited in depth-first order with respect to the graph of
 148      * interconnected modules.  Note that the given module is the first one
 149      * visited.
 150      */
 151     public void traverse(Visitor visitor, Module module) {
 152         if (visited.contains(module)) {
 153             return;
 154         }
 155         visited.add(module);
 156         if (visitor.preVisit(module)) {
 157             List<Module> imported = module.getImportedModules();
 158             for (Module m : imported) {
 159                 visitor.visit(m);
 160                 traverse(visitor, m);
 161             }
 162         }
 163         visitor.postVisit(module);
 164     }
 165 
 166     /**
 167      * @return true if any modules were traversed, false otherwise
 168      */
 169     public boolean traversedAny() {
 170         return visited.size() > 0;
 171     }
 172 
 173     /**
 174      * @return an {@code Iterator} over the {@code Module} instances which have
 175      * been visited; note that the list is ordered by the depth-first
 176      * traversal of module imports.
 177      */
 178     public Iterator<Module> iterator() {
 179         return visited.iterator();
 180     }
 181 
 182     abstract static class Visitor {
 183         private final ImportTraverser traverser;
 184 
 185         protected Visitor(ImportTraverser traverser) {
 186             this.traverser = traverser;
 187         }
 188 
 189         /**
 190          * @param m {@code Module} possibly visited by the traverser
 191          * @return true if the given {@code Module} has been visited, false
 192          * otherwise
 193          */
 194         protected boolean visited(Module m) {
 195             return traverser.visited.contains(m);
 196         }
 197 
 198         /**
 199          * Invoked with the {@code Module} identified by the client-called {@code
 200          * visit} method before any other {@code Module} instances are visited.
 201          * @param m First {@code Module} visited by the traverser
 202          */
 203         protected void init(Module m) { }
 204 
 205         /**
 206          * Invoked on a {@code Module} instance before its imports are visited.
 207          * @param m {@code Module} which imports the module about to be visited by
 208          * {@link #visit(Module)}
 209          * @return true if the {@code Module} should be visited, false otherwise.
 210          */
 211         protected boolean preVisit(Module m) {
 212             return true;
 213         }
 214 
 215         /**
 216          * Invoked on a {@code Module} instance as it is visited
 217          * @param m {@code Module} being visited
 218          */
 219         protected void visit(Module m) { }
 220 
 221         /**
 222          * Invoked on a {@code Module} instance after its imports are visited.
 223          * @param m {@code Module} which imports the module that was just visited by
 224          * {@link #visit(Module)}
 225          */
 226         protected void postVisit(Module m) { }
 227     }
 228 }