1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.interview;
  28 
  29 import java.util.ArrayList;
  30 import java.util.Comparator;
  31 import java.util.HashMap;
  32 import java.util.HashSet;
  33 import java.util.Iterator;
  34 import java.util.List;
  35 import java.util.Map;
  36 import java.util.Set;
  37 import java.util.TreeSet;
  38 
  39 /**
  40  * InterviewSet is an interview that is also a container for an ordered
  41  * set of child interviews. The default execution order for the children
  42  * is the order in which they are added to this container, but the
  43  * order can be modified by specifying dependencies between child interviews:
  44  * all dependencies for any child will be executed before the child itself.
  45  * Child interviews are added into an interview set by specifying this interview
  46  * as their parent.
  47  * The interview is invoked by using {@link #callInterview} in the usual way.
  48  * @see Interview#Interview(Interview, String)
  49  */
  50 public class InterviewSet
  51     extends Interview
  52 {
  53     /**
  54      * This exception will be thrown when an attempt to made to specify a dependency
  55      * that would create a dependency cycle. In other words, A cannot be a dependent
  56      * of B if B is already a dependent of A (either directly or indirectly.)
  57      */
  58     public static class CycleFault extends Fault
  59     {
  60         CycleFault(Interview dependent, Interview dependency) {
  61             super(i18n, "iset.cycle",
  62                   new Object[] { dependent.getTag(), dependency.getTag() } );
  63         }
  64     }
  65 
  66     /**
  67      * Create an interview set.
  68      * @param parent the parent interview for this interview
  69      * @param baseTag A name that will be used to qualify the tags of any
  70      * sub-interviews in this interview, to help ensure uniqueness of those
  71      * tags.
  72      */
  73     protected InterviewSet(Interview parent, String baseTag) {
  74         super(parent, baseTag);
  75 
  76         setFirstQuestion(sorter);
  77     }
  78 
  79     // extend Interview.add(Interview)
  80     void add(Interview child) {
  81         super.add(child);
  82         children.add(child);
  83         sortedCalls = null;
  84     }
  85 
  86     // disallow Interview.add(Question)
  87     void add(Question q) {
  88         throw new UnsupportedOperationException();
  89     }
  90 
  91     /**
  92      * Specify a dependency for a child interview.
  93      * When the interview is executed, all dependencies for each child interview
  94      * will be invoked before that child.
  95      * @param child the interview which depends on (and will be executed after)
  96      * the dependency
  97      * @param dependency the interview on which the child interview depends,
  98      * and which will be executed before the child interview
  99      * @throws InterviewSet.CycleFault if a dependency cycle would be created
 100      * @see #removeDependency
 101      */
 102     protected void addDependency(Interview child, Interview dependency)
 103         throws CycleFault
 104     {
 105         if (child == null)
 106             throw new NullPointerException();
 107 
 108         if (dependency == null)
 109             throw new NullPointerException();
 110 
 111         Set<Interview> allDeps = getAllDependencies(dependency);
 112         if (allDeps != null && allDeps.contains(child))
 113             throw new CycleFault(child, dependency);
 114 
 115         Set<Interview> deps = getDependencies(child, true);
 116         deps.add(dependency);
 117 
 118         sortedCalls = null;
 119     }
 120 
 121     /**
 122      * Remove any dependency between two interviews, and hence any ordering
 123      * constraint between these two interviews.
 124      * @param child the interview which depends on the dependency
 125      * @param dependency the interview on which the child interview depends
 126      */
 127     protected void removeDependency(Interview child, Interview dependency) {
 128         if (child == null)
 129             throw new NullPointerException();
 130 
 131         if (dependency == null)
 132             throw new NullPointerException();
 133 
 134         Set<Interview> deps = getDependencies(child, false);
 135 
 136         if (deps != null)
 137             deps.remove(dependency);
 138 
 139         if (deps.size() == 0)
 140             dependencies.remove(child);
 141 
 142         sortedCalls = null;
 143     }
 144 
 145     private Set<Interview> getDependencies(Interview child, boolean create) {
 146         Set<Interview> deps = dependencies.get(child);
 147 
 148         if (deps == null && create) {
 149             deps = new TreeSet<Interview>(new ChildComparator());
 150             dependencies.put(child, deps);
 151         }
 152 
 153         return deps;
 154     }
 155 
 156     private Set<Interview> getAllDependencies(Interview child) {
 157         Set<Interview> s = new HashSet<>();
 158         getAllDependencies(child, s);
 159         return s;
 160     }
 161 
 162     private void getAllDependencies(Interview child, Set<Interview> s) {
 163         if (s.contains(child))
 164             return;
 165 
 166         Set<Interview> deps = getDependencies(child, false);
 167         if (deps != null) {
 168             for (Iterator<Interview> iter = deps.iterator(); iter.hasNext(); ) {
 169                 Interview i = iter.next();
 170                 getAllDependencies(i, s);
 171                 s.add(i);
 172             }
 173         }
 174     }
 175 
 176     private Interview[] sortChildren() {
 177         List<Interview> list = new ArrayList<>();
 178         Set<Interview> cycleSet = new HashSet<>();
 179 
 180         for (Interview child : children) {
 181             if (!list.contains(child))
 182                 addToList(list, child, cycleSet);
 183         }
 184 
 185         for (Interview i : list) {
 186             System.err.println(i.getTag() + " " + i);
 187         }
 188 
 189         return list.toArray(new Interview[list.size()]);
 190     }
 191 
 192     private void addToList(List<Interview> list, Interview child, Set<Interview> cycleSet) {
 193         // assert !cycleSet.contains(child);
 194         if (cycleSet.contains(child))
 195             throw new IllegalArgumentException();
 196 
 197         cycleSet.add(child);
 198 
 199         Set<Interview> deps = dependencies.get(child);
 200         if (deps != null) {
 201             for (Iterator<Interview> iter = deps.iterator(); iter.hasNext(); ) {
 202                 Interview dep = iter.next();
 203                 addToList(list, dep, cycleSet);
 204             }
 205         }
 206 
 207         list.add(child);
 208 
 209         cycleSet.remove(child);
 210     }
 211 
 212     private NullQuestion sorter = new NullQuestion(this) {
 213             public boolean isEnabled() {
 214                 return false; // always hide this question
 215             }
 216 
 217             public Question getNext() {
 218                 if (sortedCalls == null) {
 219                     Interview[] cc = sortChildren();
 220 
 221                     // have to build the list from the end, backwards,
 222                     // because of the way InterviewQuestion works
 223                     Question q = qEnd;
 224                     for (int i = cc.length - 1; i >= 0; i--)
 225                         q = callInterview(cc[i], q);
 226 
 227                     sortedCalls = q;
 228                 }
 229 
 230                 return sortedCalls;
 231             }
 232         };
 233 
 234     private FinalQuestion qEnd = new FinalQuestion(this);
 235 
 236     private List<Interview> children = new ArrayList<>();
 237     private Map<Interview, Set<Interview>> dependencies = new HashMap<>();
 238     private Question sortedCalls;
 239 
 240     private class ChildComparator implements Comparator<Interview>
 241     {
 242         public int compare(Interview o1, Interview o2) {
 243             if (!children.contains(o1) || !children.contains(o2))
 244                 throw new IllegalArgumentException();
 245 
 246             if (o1 == o2)
 247                 return 0;
 248 
 249             for (Iterator<Interview> iter = children.iterator(); iter.hasNext(); ) {
 250                 Interview o = iter.next();
 251                 if (o == o1)
 252                     return -1;
 253                 if (o == o2)
 254                     return 1;
 255             }
 256 
 257             throw new IllegalStateException();
 258         }
 259     }
 260 }