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 }