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.ExternalConstructor; 022import org.apache.reef.tang.JavaConfigurationBuilder; 023import org.apache.reef.tang.Tang; 024import org.apache.reef.tang.annotations.Name; 025import org.apache.reef.tang.exceptions.BindException; 026import org.apache.reef.tang.exceptions.ClassHierarchyException; 027import org.apache.reef.tang.exceptions.NameResolutionException; 028import org.apache.reef.tang.util.MonotonicHashMap; 029import org.apache.reef.tang.util.MonotonicHashSet; 030import org.apache.reef.tang.util.ReflectionUtilities; 031 032import java.lang.reflect.Field; 033import java.lang.reflect.Modifier; 034import java.util.List; 035import java.util.Map; 036import java.util.Set; 037 038public abstract class ConfigurationModuleBuilder { 039 040 private static final Set<Class<?>> PARAM_BLACKLIST = new MonotonicHashSet<>( 041 Param.class, Impl.class); 042 private static final Set<Class<?>> PARAM_TYPES = new MonotonicHashSet<>( 043 RequiredImpl.class, OptionalImpl.class, RequiredParameter.class, 044 OptionalParameter.class); 045 protected final JavaConfigurationBuilder b = Tang.Factory.getTang() 046 .newConfigurationBuilder(); 047 // Sets of things that have been declared 048 protected final Set<Field> reqDecl = new MonotonicHashSet<>(); 049 protected final Set<Object> setOpts = new MonotonicHashSet<>(); 050 // Maps from field instance variables to the fields that they 051 // are assigned to. These better be unique! 052 protected Map<Object, Field> map = new MonotonicHashMap<>(); 053 protected final Map<Class<?>, Impl<?>> freeImpls = new MonotonicHashMap<>(); 054 protected final Map<Class<? extends Name<?>>, Param<?>> freeParams = new MonotonicHashMap<>(); 055 private final Set<Field> optDecl = new MonotonicHashSet<>(); 056 // Set of things that have been used in a bind. These must be equal 057 // to the decl counterparts before build() is called. 058 private final Set<Field> reqUsed = new MonotonicHashSet<>(); 059 private final Set<Field> optUsed = new MonotonicHashSet<>(); 060 private final Map<Class<?>, String> lateBindClazz = new MonotonicHashMap<>(); 061 062 protected ConfigurationModuleBuilder() { 063 for (final Field f : getClass().getDeclaredFields()) { 064 final Class<?> t = f.getType(); 065 if (PARAM_BLACKLIST.contains(t)) { 066 throw new ClassHierarchyException( 067 "Found a field of type " + t + " which should be a Required/Optional Parameter/Implementation instead" 068 ); 069 } 070 if (PARAM_TYPES.contains(t)) { 071 if (!Modifier.isPublic(f.getModifiers())) { 072 throw new ClassHierarchyException( 073 "Found a non-public configuration option in " + getClass() + ": " 074 + f); 075 } 076 if (!Modifier.isStatic(f.getModifiers())) { 077 throw new ClassHierarchyException( 078 "Found a non-static configuration option in " + getClass() + ": " 079 + f); 080 } 081 if (!Modifier.isFinal(f.getModifiers())) { 082 throw new ClassHierarchyException( 083 "Found a non-final configuration option in " + getClass() + ": " 084 + f); 085 } 086 final Object o; 087 try { 088 o = f.get(null); 089 } catch (IllegalArgumentException | IllegalAccessException e) { 090 throw new ClassHierarchyException( 091 "Could not look up field instance in " + getClass() + " field: " 092 + f, e); 093 } 094 if (map.containsKey(o)) { 095 throw new ClassHierarchyException( 096 "Detected aliased instances in class " + getClass() 097 + " for fields " + map.get(o) + " and " + f); 098 } 099 if (t == RequiredImpl.class || t == RequiredParameter.class) { 100 reqDecl.add(f); 101 } else { 102 optDecl.add(f); 103 } 104 map.put(o, f); 105 } 106 } 107 } 108 109 private ConfigurationModuleBuilder(final ConfigurationModuleBuilder c) { 110 try { 111 b.addConfiguration(c.b.build()); 112 } catch (final BindException e) { 113 throw new ClassHierarchyException(e); 114 } 115 reqDecl.addAll(c.reqDecl); 116 optDecl.addAll(c.optDecl); 117 reqUsed.addAll(c.reqUsed); 118 optUsed.addAll(c.optUsed); 119 setOpts.addAll(c.setOpts); 120 map.putAll(c.map); 121 freeImpls.putAll(c.freeImpls); 122 freeParams.putAll(c.freeParams); 123 lateBindClazz.putAll(c.lateBindClazz); 124 125 } 126 127 /** 128 * TODO: It would be nice if this incorporated d by reference so that static analysis / documentation tools 129 * could document the dependency between c and d. 130 * 131 * @param d a configuration module to merge 132 * @return the merged configuration module builder 133 */ 134 public final ConfigurationModuleBuilder merge(final ConfigurationModule d) { 135 if (d == null) { 136 throw new NullPointerException("If merge() was passed a static final field that is initialized to non-null, " + 137 "then this is almost certainly caused by a circular class dependency."); 138 } 139 try { 140 d.assertStaticClean(); 141 } catch (final ClassHierarchyException e) { 142 throw new ClassHierarchyException(ReflectionUtilities.getFullName(getClass()) + 143 ": detected attempt to merge with ConfigurationModule that has had set() called on it", e); 144 } 145 final ConfigurationModuleBuilder c = deepCopy(); 146 final ConfigurationModuleBuilder builder = d.getBuilder(); 147 try { 148 c.b.addConfiguration(builder.b.build()); 149 } catch (final BindException e) { 150 throw new ClassHierarchyException(e); 151 } 152 c.reqDecl.addAll(builder.reqDecl); 153 c.optDecl.addAll(builder.optDecl); 154 c.reqUsed.addAll(builder.reqUsed); 155 c.optUsed.addAll(builder.optUsed); 156 c.setOpts.addAll(builder.setOpts); 157 c.map.putAll(builder.map); 158 c.freeImpls.putAll(builder.freeImpls); 159 c.freeParams.putAll(builder.freeParams); 160 c.lateBindClazz.putAll(builder.lateBindClazz); 161 162 return c; 163 } 164 165 public final <T> ConfigurationModuleBuilder bind(final Class<?> iface, final Impl<?> opt) { 166 final ConfigurationModuleBuilder c = deepCopy(); 167 c.processUse(opt); 168 c.freeImpls.put(iface, opt); 169 return c; 170 } 171 172 public final <T> ConfigurationModuleBuilder bindSetEntry( 173 final Class<? extends Name<Set<T>>> iface, final String impl) { 174 final ConfigurationModuleBuilder c = deepCopy(); 175 try { 176 c.b.bindSetEntry(iface, impl); 177 } catch (final BindException e) { 178 throw new ClassHierarchyException(e); 179 } 180 return c; 181 } 182 183 public final <T> ConfigurationModuleBuilder bindSetEntry(final Class<? extends Name<Set<T>>> iface, 184 final Class<? extends T> impl) { 185 final ConfigurationModuleBuilder c = deepCopy(); 186 try { 187 c.b.bindSetEntry(iface, impl); 188 } catch (final BindException e) { 189 throw new ClassHierarchyException(e); 190 } 191 return c; 192 } 193 194 public final <T> ConfigurationModuleBuilder bindSetEntry(final Class<? extends Name<Set<T>>> iface, 195 final Impl<? extends T> opt) { 196 final ConfigurationModuleBuilder c = deepCopy(); 197 c.processUse(opt); 198 c.freeImpls.put(iface, opt); 199 if (!setOpts.contains(opt)) { 200 c.setOpts.add(opt); 201 } 202 return c; 203 } 204 205 public final <T> ConfigurationModuleBuilder bindSetEntry(final Class<? extends Name<Set<T>>> iface, 206 final Param<? extends T> opt) { 207 final ConfigurationModuleBuilder c = deepCopy(); 208 c.processUse(opt); 209 c.freeParams.put(iface, opt); 210 if (!setOpts.contains(opt)) { 211 c.setOpts.add(opt); 212 } 213 return c; 214 } 215 216 217 public final <T> ConfigurationModuleBuilder bindImplementation(final Class<T> iface, 218 final Class<? extends T> impl) { 219 final ConfigurationModuleBuilder c = deepCopy(); 220 try { 221 c.b.bindImplementation(iface, impl); 222 } catch (final BindException e) { 223 throw new ClassHierarchyException(e); 224 } 225 return c; 226 } 227 228 public final <T> ConfigurationModuleBuilder bindImplementation(final Class<T> iface, 229 final String impl) { 230 final ConfigurationModuleBuilder c = deepCopy(); 231 c.lateBindClazz.put(iface, impl); 232 return c; 233 } 234 235 public final <T> ConfigurationModuleBuilder bindImplementation(final Class<T> iface, 236 final Impl<? extends T> opt) { 237 final ConfigurationModuleBuilder c = deepCopy(); 238 c.processUse(opt); 239 c.freeImpls.put(iface, opt); 240 return c; 241 } 242 243 public final <T> ConfigurationModuleBuilder bindNamedParameter( 244 final Class<? extends Name<T>> name, final String value) { 245 final ConfigurationModuleBuilder c = deepCopy(); 246 try { 247 c.b.bindNamedParameter(name, value); 248 } catch (final BindException e) { 249 throw new ClassHierarchyException(e); 250 } 251 return c; 252 } 253 254 public final <T> ConfigurationModuleBuilder bindNamedParameter( 255 final Class<? extends Name<T>> name, final Param<T> opt) { 256 final ConfigurationModuleBuilder c = deepCopy(); 257 c.processUse(opt); 258 c.freeParams.put(name, opt); 259 return c; 260 } 261 262 public final <T> ConfigurationModuleBuilder bindNamedParameter( 263 final Class<? extends Name<T>> iface, final Class<? extends T> impl) { 264 final ConfigurationModuleBuilder c = deepCopy(); 265 try { 266 c.b.bindNamedParameter(iface, impl); 267 } catch (final BindException e) { 268 throw new ClassHierarchyException(e); 269 } 270 return c; 271 } 272 273 public final <T> ConfigurationModuleBuilder bindNamedParameter( 274 final Class<? extends Name<T>> iface, final Impl<? extends T> opt) { 275 final ConfigurationModuleBuilder c = deepCopy(); 276 c.processUse(opt); 277 c.freeImpls.put(iface, opt); 278 return c; 279 } 280 281 public final <T> ConfigurationModuleBuilder bindConstructor(final Class<T> clazz, 282 final Class<? extends ExternalConstructor<? extends T>> 283 constructor) { 284 final ConfigurationModuleBuilder c = deepCopy(); 285 try { 286 c.b.bindConstructor(clazz, constructor); 287 } catch (final BindException e) { 288 throw new ClassHierarchyException(e); 289 } 290 return c; 291 } 292 293 public final <T> ConfigurationModuleBuilder bindConstructor( 294 final Class<T> cons, final Impl<? extends ExternalConstructor<? extends T>> v) { 295 final ConfigurationModuleBuilder c = deepCopy(); 296 c.processUse(v); 297 c.freeImpls.put(cons, v); 298 return c; 299 } 300 301 public final <T> ConfigurationModuleBuilder bindList(final Class<? extends Name<List<T>>> iface, 302 final Impl<List> opt) { 303 final ConfigurationModuleBuilder c = deepCopy(); 304 c.processUse(opt); 305 c.freeImpls.put(iface, opt); 306 return c; 307 } 308 309 public final <T> ConfigurationModuleBuilder bindList(final Class<? extends Name<List<T>>> iface, 310 final Param<List> opt) { 311 final ConfigurationModuleBuilder c = deepCopy(); 312 c.processUse(opt); 313 c.freeParams.put(iface, opt); 314 return c; 315 } 316 317 public final <T> ConfigurationModuleBuilder bindList(final Class<? extends Name<List<T>>> iface, final List list) { 318 final ConfigurationModuleBuilder c = deepCopy(); 319 c.b.bindList(iface, list); 320 return c; 321 } 322 323 private <T> void processUse(final Object impl) { 324 final Field f = map.get(impl); 325 if (f == null) { /* throw */ 326 throw new ClassHierarchyException("Unknown Impl/Param when binding " + 327 ReflectionUtilities.getSimpleName(impl.getClass()) + ". Did you pass in a field from some other module?"); 328 } 329 if (!reqUsed.contains(f)) { 330 reqUsed.add(f); 331 } 332 if (!optUsed.contains(f)) { 333 optUsed.add(f); 334 } 335 } 336 337 public final ConfigurationModule build() throws ClassHierarchyException { 338 final ConfigurationModuleBuilder c = deepCopy(); 339 340 if (!(c.reqUsed.containsAll(c.reqDecl) && c.optUsed.containsAll(c.optDecl))) { 341 final Set<Field> fset = new MonotonicHashSet<>(); 342 for (final Field f : c.reqDecl) { 343 if (!c.reqUsed.contains(f)) { 344 fset.add(f); 345 } 346 } 347 for (final Field f : c.optDecl) { 348 if (!c.optUsed.contains(f)) { 349 fset.add(f); 350 } 351 } 352 throw new ClassHierarchyException( 353 "Found declared options that were not used in binds: " 354 + toString(fset)); 355 } 356 for (final Class<?> clz : c.lateBindClazz.keySet()) { 357 try { 358 c.b.bind(ReflectionUtilities.getFullName(clz), c.lateBindClazz.get(clz)); 359 } catch (final NameResolutionException e) { 360 throw new ClassHierarchyException("ConfigurationModule refers to unknown class: " + 361 c.lateBindClazz.get(clz), e); 362 } catch (final BindException e) { 363 throw new ClassHierarchyException("bind failed while initializing ConfigurationModuleBuilder", e); 364 } 365 } 366 return new ConfigurationModule(c); 367 } 368 369/* public final <T> ConfigurationModuleBuilder bind(Class<T> iface, Class<?> impl) { 370 ConfigurationModuleBuilder c = deepCopy(); 371 try { 372 c.b.bind(iface, impl); 373 } catch (BindException e) { 374 throw new ClassHierarchyException(e); 375 } 376 return c; 377 } */ 378 379 final ConfigurationModuleBuilder deepCopy() { 380 // ooh... this is a dirty trick --- we strip this's type off here, 381 // fortunately, we've all ready looked at the root object's class's 382 // fields, and we copy the information we extracted from them, so 383 // everything works out OK w.r.t. field detection. 384 return new ConfigurationModuleBuilder(this) { 385 }; 386 } 387 388 final String toString(final Set<Field> s) { 389 final StringBuilder sb = new StringBuilder("{"); 390 boolean first = true; 391 for (final Field f : s) { 392 sb.append((first ? " " : ", ") + f.getName()); 393 first = false; 394 } 395 sb.append(" }"); 396 return sb.toString(); 397 } 398}