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 visit(Repository repo,
  64                       String name,
  65                       VersionConstraint constraint)
  66             throws ModuleInitializationException{
  67 
  68         visit(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 visit(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(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 visit(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             init(m);
 141             visit(repo, m);
 142         }
 143     }
 144 
 145     public void visit(Repository repo,
 146                        Module module) {
 147                        
 148         if (visited.contains(module)) {
 149             return;
 150         }
 151         visited.add(module);
 152         if (preVisit(module)) {
 153             List<Module> imported = module.getImportedModules();
 154             for (Module m : imported) {
 155                 visit(m);
 156                 visit(repo, m);
 157             }
 158         }
 159         postVisit(module);
 160     }
 161 
 162     /**
 163      * @return an unmodifiable list of the {@code Module} instances which have
 164      * been visited; note that the list is ordered by the depth-first
 165      * traversal of module imports.
 166      */
 167     public Iterator<Module> iterator() {
 168         return visited.iterator();
 169     }
 170 
 171     /**
 172      * @param m {@code Module} possibly visited by the traverser
 173      * @return true if the given {@code Module} has been visited, false
 174      * otherwise
 175      */
 176     protected boolean visited(Module m) {
 177         return visited.contains(m);
 178     }
 179 
 180     /**
 181      * Invoked with the {@code Module} identified by the client-called {@code
 182      * visit} method before any other {@code Module} instances are visited.
 183      * @param m First {@code Module} visited by the traverser
 184      */
 185     protected void init(Module m) { }
 186 
 187     /**
 188      * Invoked on a {@code Module} instance before its imports are visited.
 189      * @param m {@code Module} which imports the module about to be visited by
 190      * {@link #visit(Module)}
 191      * @return true if the {@code Module} should be visited, false otherwise.
 192      */
 193     protected boolean preVisit(Module m) {
 194         return true;
 195     }
 196 
 197     /**
 198      * Invoked on a {@code Module} instance as it is visited
 199      * @param m {@code Module} being visited
 200      */
 201     protected void visit(Module m) { }
 202 
 203     /**
 204      * Invoked on a {@code Module} instance after its imports are visited.
 205      * @param m {@code Module} which imports the module that was just visited by
 206      * {@link #visit(Module)}
 207      */
 208     protected void postVisit(Module m) { }
 209 }