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.implementation.ConfigurationBuilderImpl; 027import org.apache.reef.tang.implementation.ConfigurationImpl; 028import org.apache.reef.tang.types.ClassNode; 029import org.apache.reef.tang.types.ConstructorArg; 030import org.apache.reef.tang.types.NamedParameterNode; 031import org.apache.reef.tang.types.Node; 032import org.apache.reef.tang.util.*; 033 034import java.lang.reflect.Field; 035import java.util.ArrayList; 036import java.util.List; 037import java.util.Map; 038import java.util.Map.Entry; 039import java.util.Set; 040import java.util.logging.Level; 041import java.util.logging.Logger; 042 043/** 044 * Allows applications to bundle sets of configuration options together into 045 * discrete packages. Unlike more conventional approaches, 046 * ConfigurationModules store such information in static data structures that 047 * can be statically discovered and sanity-checked. 048 * 049 * See org.apache.reef.tang.formats.TestConfigurationModule for more information and examples. 050 */ 051public class ConfigurationModule { 052 053 private static final Logger LOG = Logger.getLogger(ConfigurationModule.class.getName()); 054 055 private final ConfigurationModuleBuilder builder; 056 // Set of required unset parameters. Must be empty before build. 057 private final Set<Field> reqSet = new MonotonicHashSet<>(); 058 private final Map<Impl<?>, Class<?>> setImpls = new MonotonicHashMap<>(); 059 private final MonotonicMultiHashMap<Impl<?>, Class<?>> setImplSets = new MonotonicMultiHashMap<>(); 060 private final MonotonicMultiHashMap<Impl<?>, String> setLateImplSets = new MonotonicMultiHashMap<>(); 061 private final MonotonicMultiHashMap<Param<?>, String> setParamSets = new MonotonicMultiHashMap<>(); 062 private final Map<Impl<?>, String> setLateImpls = new MonotonicHashMap<>(); 063 private final Map<Param<?>, String> setParams = new MonotonicHashMap<>(); 064 private final Map<Impl<List>, List<?>> setImplLists = new MonotonicHashMap<>(); 065 private final Map<Param<List>, List<?>> setParamLists = new MonotonicHashMap<>(); 066 067 protected ConfigurationModule(final ConfigurationModuleBuilder builder) { 068 this.builder = builder.deepCopy(); 069 } 070 071 private ConfigurationModule deepCopy() { 072 final ConfigurationModule cm = new ConfigurationModule(builder.deepCopy()); 073 cm.setImpls.putAll(setImpls); 074 cm.setImplSets.addAll(setImplSets); 075 cm.setLateImplSets.addAll(setLateImplSets); 076 cm.setParamSets.addAll(setParamSets); 077 cm.setLateImpls.putAll(setLateImpls); 078 cm.setParams.putAll(setParams); 079 cm.reqSet.addAll(reqSet); 080 cm.setImplLists.putAll(setImplLists); 081 cm.setParamLists.putAll(setParamLists); 082 return cm; 083 } 084 085 private <T> void processSet(final Object impl) { 086 final Field f = builder.map.get(impl); 087 if (f == null) { /* throw */ 088 throw new ClassHierarchyException("Unknown Impl/Param when setting " + 089 ReflectionUtilities.getSimpleName(impl.getClass()) + ". Did you pass in a field from some other module?"); 090 } 091 if (!reqSet.contains(f)) { 092 reqSet.add(f); 093 } 094 } 095 096 public final <T> ConfigurationModule set(final Impl<T> opt, final Class<? extends T> impl) { 097 final ConfigurationModule c = deepCopy(); 098 c.processSet(opt); 099 if (c.builder.setOpts.contains(opt)) { 100 c.setImplSets.put(opt, impl); 101 } else { 102 c.setImpls.put(opt, impl); 103 } 104 return c; 105 } 106 107 public final <T> ConfigurationModule set(final Impl<T> opt, final String impl) { 108 final ConfigurationModule c = deepCopy(); 109 c.processSet(opt); 110 if (c.builder.setOpts.contains(opt)) { 111 c.setLateImplSets.put(opt, impl); 112 } else { 113 c.setLateImpls.put(opt, impl); 114 } 115 return c; 116 } 117 118 /** 119 * Binds a list to a specific optional/required Impl using ConfigurationModule. 120 * 121 * @param opt Target optional/required Impl 122 * @param implList List object to be injected 123 * @param <T> a type 124 * @return the configuration module 125 */ 126 public final <T> ConfigurationModule set(final Impl<List> opt, final List implList) { 127 final ConfigurationModule c = deepCopy(); 128 c.processSet(opt); 129 c.setImplLists.put(opt, implList); 130 return c; 131 } 132 133 public final <T> ConfigurationModule set(final Param<T> opt, final Class<? extends T> val) { 134 return set(opt, ReflectionUtilities.getFullName(val)); 135 } 136 137 public final ConfigurationModule set(final Param<Boolean> opt, final boolean val) { 138 return set(opt, "" + val); 139 } 140 141 public final ConfigurationModule set(final Param<? extends Number> opt, final Number val) { 142 return set(opt, "" + val); 143 } 144 145 public final <T> ConfigurationModule set(final Param<T> opt, final String val) { 146 final ConfigurationModule c = deepCopy(); 147 c.processSet(opt); 148 if (c.builder.setOpts.contains(opt)) { 149 c.setParamSets.put(opt, val); 150 } else { 151 c.setParams.put(opt, val); 152 } 153 return c; 154 } 155 156 /** 157 * Binds a set of values to a Param using ConfigurationModule. 158 * 159 * @param opt Target Param 160 * @param values Values to bind to the Param 161 * @param <T> type 162 * @return the Configuration module 163 */ 164 public final <T> ConfigurationModule setMultiple(final Param<T> opt, final Iterable<String> values) { 165 ConfigurationModule c = deepCopy(); 166 for (final String val : values) { 167 c = c.set(opt, val); 168 } 169 return c; 170 } 171 172 /** 173 * Binds a set of values to a Param using ConfigurationModule. 174 * 175 * @param opt Target Param 176 * @param values Values to bind to the Param 177 * @param <T> type 178 * @return the Configuration module 179 */ 180 public final <T> ConfigurationModule setMultiple(final Param<T> opt, final String... values) { 181 ConfigurationModule c = deepCopy(); 182 for (final String val : values) { 183 c = c.set(opt, val); 184 } 185 return c; 186 } 187 188 /** 189 * Binds a list to a specific optional/required Param using ConfigurationModule. 190 * 191 * @param opt target optional/required Param 192 * @param implList List object to be injected 193 * @param <T> type 194 * @return the Configuration module 195 */ 196 public final <T> ConfigurationModule set(final Param<List> opt, final List implList) { 197 final ConfigurationModule c = deepCopy(); 198 c.processSet(opt); 199 c.setParamLists.put(opt, implList); 200 return c; 201 } 202 203 @SuppressWarnings({"unchecked", "rawtypes"}) 204 public Configuration build() throws BindException { 205 final ConfigurationModule c = deepCopy(); 206 207 //TODO[REEF-968] check that required parameters have not been set to null 208 209 if (!c.reqSet.containsAll(c.builder.reqDecl)) { 210 final Set<Field> missingSet = new MonotonicHashSet<>(); 211 for (final Field f : c.builder.reqDecl) { 212 if (!c.reqSet.contains(f)) { 213 missingSet.add(f); 214 } 215 } 216 throw new BindException( 217 "Attempt to build configuration before setting required option(s): " 218 + builder.toString(missingSet)); 219 } 220 221 for (final Class<?> clazz : c.builder.freeImpls.keySet()) { 222 final Impl<?> i = c.builder.freeImpls.get(clazz); 223 boolean foundOne = false; 224 if (c.setImpls.containsKey(i)) { 225 if (c.setImpls.get(i) != null) { 226 c.builder.b.bind(clazz, c.setImpls.get(i)); 227 foundOne = true; 228 } 229 } else if (c.setLateImpls.containsKey(i)) { 230 if (c.setLateImpls.get(i) != null) { 231 c.builder.b.bind(ReflectionUtilities.getFullName(clazz), c.setLateImpls.get(i)); 232 foundOne = true; 233 } 234 } else if (c.setImplSets.containsKey(i) || c.setLateImplSets.containsKey(i)) { 235 if (c.setImplSets.getValuesForKey(i) != null) { 236 for (final Class<?> clz : c.setImplSets.getValuesForKey(i)) { 237 c.builder.b.bindSetEntry((Class) clazz, (Class) clz); 238 } 239 foundOne = true; 240 } 241 if (c.setLateImplSets.getValuesForKey(i) != null) { 242 for (final String s : c.setLateImplSets.getValuesForKey(i)) { 243 c.builder.b.bindSetEntry((Class) clazz, s); 244 } 245 foundOne = true; 246 } 247 } else if (c.setImplLists.containsKey(i)) { 248 if (c.setImplLists.get(i) != null) { 249 c.builder.b.bindList((Class) clazz, c.setImplLists.get(i)); 250 foundOne = true; 251 } 252 } 253 if(!foundOne && !(i instanceof OptionalImpl)) { 254 final IllegalStateException e = 255 new IllegalStateException("Cannot find the value for the RequiredImplementation of the " + clazz 256 + ". Check that you don't pass null as an implementation value."); 257 LOG.log(Level.SEVERE, "Failed to build configuration", e); 258 throw e; 259 } 260 } 261 for (final Class<? extends Name<?>> clazz : c.builder.freeParams.keySet()) { 262 final Param<?> p = c.builder.freeParams.get(clazz); 263 final String s = c.setParams.get(p); 264 boolean foundOne = false; 265 if (s != null) { 266 c.builder.b.bindNamedParameter(clazz, s); 267 foundOne = true; 268 } 269 // Find the bound list for the NamedParameter 270 final List list = c.setParamLists.get(p); 271 if (list != null) { 272 c.builder.b.bindList((Class) clazz, list); 273 foundOne = true; 274 } 275 for (final String paramStr : c.setParamSets.getValuesForKey(p)) { 276 c.builder.b.bindSetEntry((Class) clazz, paramStr); 277 foundOne = true; 278 } 279 280 if (!foundOne && !(p instanceof OptionalParameter)) { 281 final IllegalStateException e = 282 new IllegalStateException("Cannot find the value for the RequiredParameter of the " + clazz 283 + ". Check that you don't pass null as the parameter value."); 284 LOG.log(Level.SEVERE, "Failed to build configuration", e); 285 throw e; 286 } 287 288 } 289 290 return c.builder.b.build(); 291 292 } 293 294 295 public Set<NamedParameterNode<?>> getBoundNamedParameters() { 296 final Configuration c = this.builder.b.build(); 297 final Set<NamedParameterNode<?>> nps = new MonotonicSet<>(); 298 nps.addAll(c.getNamedParameters()); 299 for (final Class<?> np : this.builder.freeParams.keySet()) { 300 try { 301 nps.add((NamedParameterNode<?>) builder.b.getClassHierarchy().getNode(ReflectionUtilities.getFullName(np))); 302 } catch (final NameResolutionException e) { 303 throw new IllegalStateException(e); 304 } 305 } 306 return nps; 307 } 308 309 /** 310 * Replace any \'s in the input string with \\. and any "'s with \". 311 * 312 * @param in 313 * @return 314 */ 315 private static String escape(final String in) { 316 // After regexp escaping \\\\ = 1 slash, \\\\\\\\ = 2 slashes. 317 318 // Also, the second args of replaceAll are neither strings nor regexps, and 319 // are instead a special DSL used by Matcher. Therefore, we need to double 320 // escape slashes (4 slashes) and quotes (3 slashes + ") in those strings. 321 // Since we need to write \\ and \", we end up with 8 and 7 slashes, 322 // respectively. 323 return in.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\\\""); 324 } 325 326 private static StringBuilder join(final StringBuilder sb, final String sep, final ConstructorArg[] types) { 327 if (types.length > 0) { 328 sb.append(types[0].getType()); 329 for (int i = 1; i < types.length; i++) { 330 sb.append(sep).append(types[i].getType()); 331 } 332 } 333 return sb; 334 } 335 336 /** 337 * Convert Configuration to a list of strings formatted as "param=value". 338 * 339 * @param c 340 * @return 341 */ 342 private static List<String> toConfigurationStringList(final Configuration c) { 343 final ConfigurationImpl conf = (ConfigurationImpl) c; 344 final List<String> l = new ArrayList<>(); 345 for (final ClassNode<?> opt : conf.getBoundImplementations()) { 346 l.add(opt.getFullName() 347 + '=' 348 + escape(conf.getBoundImplementation(opt).getFullName())); 349 } 350 for (final ClassNode<?> opt : conf.getBoundConstructors()) { 351 l.add(opt.getFullName() 352 + '=' 353 + escape(conf.getBoundConstructor(opt).getFullName())); 354 } 355 for (final NamedParameterNode<?> opt : conf.getNamedParameters()) { 356 l.add(opt.getFullName() 357 + '=' 358 + escape(conf.getNamedParameter(opt))); 359 } 360 for (final ClassNode<?> cn : conf.getLegacyConstructors()) { 361 final StringBuilder sb = new StringBuilder(); 362 join(sb, "-", conf.getLegacyConstructor(cn).getArgs()); 363 l.add(cn.getFullName() 364 + escape('=' 365 + ConfigurationBuilderImpl.INIT 366 + '(' 367 + sb.toString() 368 + ')' 369 )); 370 } 371 for (final NamedParameterNode<Set<?>> key : conf.getBoundSets()) { 372 for (final Object value : conf.getBoundSet(key)) { 373 final String val; 374 if (value instanceof String) { 375 val = (String) value; 376 } else if (value instanceof Node) { 377 val = ((Node) value).getFullName(); 378 } else { 379 throw new IllegalStateException("The value bound to a given NamedParameterNode " 380 + key + " is neither the set of class hierarchy nodes nor strings."); 381 } 382 l.add(key.getFullName() + '=' + escape(val)); 383 } 384 } 385 return l; 386 } 387 388 public List<Entry<String, String>> toStringPairs() { 389 final List<Entry<String, String>> ret = new ArrayList<>(); 390 class MyEntry implements Entry<String, String> { 391 private final String k; 392 private final String v; 393 394 MyEntry(final String k, final String v) { 395 this.k = k; 396 this.v = v; 397 } 398 399 @Override 400 public String getKey() { 401 return k; 402 } 403 404 @Override 405 public String getValue() { 406 return v; 407 } 408 409 @Override 410 public String setValue(final String value) { 411 throw new UnsupportedOperationException(); 412 } 413 414 } 415 for (final Class<?> c : this.builder.freeParams.keySet()) { 416 ret.add(new MyEntry(ReflectionUtilities.getFullName(c), 417 this.builder.map.get(this.builder.freeParams.get(c)).getName())); 418 } 419 for (final Class<?> c : this.builder.freeImpls.keySet()) { 420 ret.add(new MyEntry(ReflectionUtilities.getFullName(c), 421 this.builder.map.get(this.builder.freeImpls.get(c)).getName())); 422 } 423 for (final String s : toConfigurationStringList(builder.b.build())) { 424 final String[] tok = s.split("=", 2); 425 ret.add(new MyEntry(tok[0], tok[1])); 426 } 427 428 return ret; 429 } 430 431 public String toPrettyString() { 432 final StringBuilder sb = new StringBuilder(); 433 434 for (final Entry<String, String> l : toStringPairs()) { 435 sb.append(l.getKey() + "=" + l.getValue() + "\n"); 436 } 437 return sb.toString(); 438 } 439 440 public void assertStaticClean() throws ClassHierarchyException { 441 if (!( 442 setImpls.isEmpty() && 443 setImplSets.isEmpty() && 444 setLateImplSets.isEmpty() && 445 setParamSets.isEmpty() && 446 setLateImpls.isEmpty() && 447 setParams.isEmpty() && 448 setImplLists.isEmpty() && 449 setParamLists.isEmpty() 450 )) { 451 throw new ClassHierarchyException("Detected statically set ConfigurationModule Parameter / Implementation. " + 452 "set() should only be used dynamically. Use bind...() instead."); 453 } 454 } 455 456 public ConfigurationModuleBuilder getBuilder() { 457 return builder; 458 } 459}