001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.reef.tang.formats; 020 021import org.apache.reef.tang.Configuration; 022import org.apache.reef.tang.annotations.Name; 023import org.apache.reef.tang.exceptions.BindException; 024import org.apache.reef.tang.exceptions.ClassHierarchyException; 025import org.apache.reef.tang.exceptions.NameResolutionException; 026import org.apache.reef.tang.types.NamedParameterNode; 027import org.apache.reef.tang.util.*; 028 029import java.lang.reflect.Field; 030import java.util.ArrayList; 031import java.util.List; 032import java.util.Map; 033import java.util.Map.Entry; 034import java.util.Set; 035 036/** 037 * Allows applications to bundle sets of configuration options together into 038 * discrete packages. Unlike more conventional approaches, 039 * ConfigurationModules store such information in static data structures that 040 * can be statically discovered and sanity-checked. 041 * 042 * @see org.apache.reef.tang.formats.TestConfigurationModule for more information and examples. 043 */ 044public class ConfigurationModule { 045 final ConfigurationModuleBuilder builder; 046 // Set of required unset parameters. Must be empty before build. 047 private final Set<Field> reqSet = new MonotonicHashSet<>(); 048 private final Map<Impl<?>, Class<?>> setImpls = new MonotonicHashMap<>(); 049 private final MonotonicMultiHashMap<Impl<?>, Class<?>> setImplSets = new MonotonicMultiHashMap<>(); 050 private final MonotonicMultiHashMap<Impl<?>, String> setLateImplSets = new MonotonicMultiHashMap<>(); 051 private final MonotonicMultiHashMap<Param<?>, String> setParamSets = new MonotonicMultiHashMap<>(); 052 private final Map<Impl<?>, String> setLateImpls = new MonotonicHashMap<>(); 053 private final Map<Param<?>, String> setParams = new MonotonicHashMap<>(); 054 private final Map<Impl<List>, List<?>> setImplLists = new MonotonicHashMap<>(); 055 private final Map<Param<List>, List<?>> setParamLists = new MonotonicHashMap<>(); 056 057 protected ConfigurationModule(ConfigurationModuleBuilder builder) { 058 this.builder = builder.deepCopy(); 059 } 060 061 private ConfigurationModule deepCopy() { 062 ConfigurationModule cm = new ConfigurationModule(builder.deepCopy()); 063 cm.setImpls.putAll(setImpls); 064 cm.setImplSets.addAll(setImplSets); 065 cm.setLateImplSets.addAll(setLateImplSets); 066 cm.setParamSets.addAll(setParamSets); 067 cm.setLateImpls.putAll(setLateImpls); 068 cm.setParams.putAll(setParams); 069 cm.reqSet.addAll(reqSet); 070 cm.setImplLists.putAll(setImplLists); 071 cm.setParamLists.putAll(setParamLists); 072 return cm; 073 } 074 075 private final <T> void processSet(Object impl) { 076 Field f = builder.map.get(impl); 077 if (f == null) { /* throw */ 078 throw new ClassHierarchyException("Unknown Impl/Param when setting " + ReflectionUtilities.getSimpleName(impl.getClass()) + ". Did you pass in a field from some other module?"); 079 } 080 if (!reqSet.contains(f)) { 081 reqSet.add(f); 082 } 083 } 084 085 public final <T> ConfigurationModule set(Impl<T> opt, Class<? extends T> impl) { 086 ConfigurationModule c = deepCopy(); 087 c.processSet(opt); 088 if (c.builder.setOpts.contains(opt)) { 089 c.setImplSets.put(opt, impl); 090 } else { 091 c.setImpls.put(opt, impl); 092 } 093 return c; 094 } 095 096 public final <T> ConfigurationModule set(Impl<T> opt, String impl) { 097 ConfigurationModule c = deepCopy(); 098 c.processSet(opt); 099 if (c.builder.setOpts.contains(opt)) { 100 c.setLateImplSets.put(opt, impl); 101 } else { 102 c.setLateImpls.put(opt, impl); 103 } 104 return c; 105 } 106 107 /** 108 * Binds a list to a specific optional/required Impl using ConfigurationModule. 109 * 110 * @param opt Target optional/required Impl 111 * @param implList List object to be injected 112 * @param <T> 113 * @return 114 */ 115 public final <T> ConfigurationModule set(Impl<List> opt, List implList) { 116 ConfigurationModule c = deepCopy(); 117 c.processSet(opt); 118 c.setImplLists.put(opt, implList); 119 return c; 120 } 121 122 public final <T> ConfigurationModule set(Param<T> opt, Class<? extends T> val) { 123 return set(opt, ReflectionUtilities.getFullName(val)); 124 } 125 126 public final ConfigurationModule set(Param<Boolean> opt, boolean val) { 127 return set(opt, "" + val); 128 } 129 130 public final ConfigurationModule set(Param<? extends Number> opt, Number val) { 131 return set(opt, "" + val); 132 } 133 134 public final <T> ConfigurationModule set(Param<T> opt, String val) { 135 ConfigurationModule c = deepCopy(); 136 c.processSet(opt); 137 if (c.builder.setOpts.contains(opt)) { 138 c.setParamSets.put(opt, val); 139 } else { 140 c.setParams.put(opt, val); 141 } 142 return c; 143 } 144 145 /** 146 * Binds a list to a specfici optional/required Param using ConfigurationModule. 147 * 148 * @param opt target optional/required Param 149 * @param implList List object to be injected 150 * @param <T> 151 * @return 152 */ 153 public final <T> ConfigurationModule set(Param<List> opt, List implList) { 154 ConfigurationModule c = deepCopy(); 155 c.processSet(opt); 156 c.setParamLists.put(opt, implList); 157 return c; 158 } 159 160 @SuppressWarnings({"unchecked", "rawtypes"}) 161 public Configuration build() throws BindException { 162 ConfigurationModule c = deepCopy(); 163 164 if (!c.reqSet.containsAll(c.builder.reqDecl)) { 165 Set<Field> missingSet = new MonotonicHashSet<>(); 166 for (Field f : c.builder.reqDecl) { 167 if (!c.reqSet.contains(f)) { 168 missingSet.add(f); 169 } 170 } 171 throw new BindException( 172 "Attempt to build configuration before setting required option(s): " 173 + builder.toString(missingSet)); 174 } 175 176 for (Class<?> clazz : c.builder.freeImpls.keySet()) { 177 Impl<?> i = c.builder.freeImpls.get(clazz); 178 if (c.setImpls.containsKey(i)) { 179 c.builder.b.bind(clazz, c.setImpls.get(i)); 180 } else if (c.setLateImpls.containsKey(i)) { 181 c.builder.b.bind(ReflectionUtilities.getFullName(clazz), c.setLateImpls.get(i)); 182 } else if (c.setImplSets.containsKey(i) || c.setLateImplSets.containsKey(i)) { 183 for (Class<?> clz : c.setImplSets.getValuesForKey(i)) { 184 c.builder.b.bindSetEntry((Class) clazz, (Class) clz); 185 } 186 for (String s : c.setLateImplSets.getValuesForKey(i)) { 187 c.builder.b.bindSetEntry((Class) clazz, s); 188 } 189 } else if (c.setImplLists.containsKey(i)) { 190 c.builder.b.bindList((Class) clazz, c.setImplLists.get(i)); 191 } 192 } 193 for (Class<? extends Name<?>> clazz : c.builder.freeParams.keySet()) { 194 Param<?> p = c.builder.freeParams.get(clazz); 195 String s = c.setParams.get(p); 196 boolean foundOne = false; 197 if (s != null) { 198 c.builder.b.bindNamedParameter(clazz, s); 199 foundOne = true; 200 } 201 // Find the bound list for the NamedParameter 202 List list = c.setParamLists.get(p); 203 if (list != null) { 204 c.builder.b.bindList((Class) clazz, list); 205 foundOne = true; 206 } 207 for (String paramStr : c.setParamSets.getValuesForKey(p)) { 208 c.builder.b.bindSetEntry((Class) clazz, paramStr); 209 foundOne = true; 210 } 211 if (!foundOne) { 212 if (!(p instanceof OptionalParameter)) { 213 throw new IllegalStateException(); 214 } 215 } 216 } 217 return c.builder.b.build(); 218 219 } 220 221 public Set<NamedParameterNode<?>> getBoundNamedParameters() { 222 Configuration c = this.builder.b.build(); 223 Set<NamedParameterNode<?>> nps = new MonotonicSet<>(); 224 nps.addAll(c.getNamedParameters()); 225 for (Class<?> np : this.builder.freeParams.keySet()) { 226 try { 227 nps.add((NamedParameterNode<?>) builder.b.getClassHierarchy().getNode(ReflectionUtilities.getFullName(np))); 228 } catch (NameResolutionException e) { 229 throw new IllegalStateException(e); 230 } 231 } 232 return nps; 233 } 234 235 public List<Entry<String, String>> toStringPairs() { 236 List<Entry<String, String>> ret = new ArrayList<>(); 237 class MyEntry implements Entry<String, String> { 238 final String k; 239 final String v; 240 241 public MyEntry(String k, String v) { 242 this.k = k; 243 this.v = v; 244 } 245 246 @Override 247 public String getKey() { 248 return k; 249 } 250 251 @Override 252 public String getValue() { 253 return v; 254 } 255 256 @Override 257 public String setValue(String value) { 258 throw new UnsupportedOperationException(); 259 } 260 261 } 262 for (Class<?> c : this.builder.freeParams.keySet()) { 263 ret.add(new MyEntry(ReflectionUtilities.getFullName(c), this.builder.map.get(this.builder.freeParams.get(c)).getName())); 264 } 265 for (Class<?> c : this.builder.freeImpls.keySet()) { 266 ret.add(new MyEntry(ReflectionUtilities.getFullName(c), this.builder.map.get(this.builder.freeImpls.get(c)).getName())); 267 } 268 for (String s : ConfigurationFile.toConfigurationStringList(builder.b.build())) { 269 String[] tok = s.split("=", 2); 270 ret.add(new MyEntry(tok[0], tok[1])); 271 } 272 273 return ret; 274 } 275 276 public String toPrettyString() { 277 StringBuilder sb = new StringBuilder(); 278 279 for (Entry<String, String> l : toStringPairs()) { 280 sb.append(l.getKey() + "=" + l.getValue() + "\n"); 281 } 282 return sb.toString(); 283 } 284 285 public void assertStaticClean() throws ClassHierarchyException { 286 if (!( 287 setImpls.isEmpty() && 288 setImplSets.isEmpty() && 289 setLateImplSets.isEmpty() && 290 setParamSets.isEmpty() && 291 setLateImpls.isEmpty() && 292 setParams.isEmpty() && 293 setImplLists.isEmpty() && 294 setParamLists.isEmpty() 295 )) { 296 throw new ClassHierarchyException("Detected statically set ConfigurationModule Parameter / Implementation. set() should only be used dynamically. Use bind...() instead."); 297 } 298 } 299}