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 final static Set<Class<?>> paramBlacklist = new MonotonicHashSet<Class<?>>( 041 Param.class, Impl.class); 042 private final static Set<Class<?>> paramTypes = new MonotonicHashSet<Class<?>>( 043 RequiredImpl.class, OptionalImpl.class, RequiredParameter.class, 044 OptionalParameter.class); 045 final JavaConfigurationBuilder b = Tang.Factory.getTang() 046 .newConfigurationBuilder(); 047 // Sets of things that have been declared 048 final Set<Field> reqDecl = new MonotonicHashSet<>(); 049 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 final Map<Object, Field> map = new MonotonicHashMap<>(); 053 final Map<Class<?>, Impl<?>> freeImpls = new MonotonicHashMap<>(); 054 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 (Field f : getClass().getDeclaredFields()) { 064 Class<?> t = f.getType(); 065 if (paramBlacklist.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 (paramTypes.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(ConfigurationModuleBuilder c) { 110 try { 111 b.addConfiguration(c.b.build()); 112 } catch (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 public final ConfigurationModuleBuilder merge(ConfigurationModule d) { 132 if (d == null) { 133 throw new NullPointerException("If merge() was passed a static final field that is initialized to non-null, then this is almost certainly caused by a circular class dependency."); 134 } 135 try { 136 d.assertStaticClean(); 137 } catch (ClassHierarchyException e) { 138 throw new ClassHierarchyException(ReflectionUtilities.getFullName(getClass()) + ": detected attempt to merge with ConfigurationModule that has had set() called on it", e); 139 } 140 ConfigurationModuleBuilder c = deepCopy(); 141 try { 142 c.b.addConfiguration(d.builder.b.build()); 143 } catch (BindException e) { 144 throw new ClassHierarchyException(e); 145 } 146 c.reqDecl.addAll(d.builder.reqDecl); 147 c.optDecl.addAll(d.builder.optDecl); 148 c.reqUsed.addAll(d.builder.reqUsed); 149 c.optUsed.addAll(d.builder.optUsed); 150 c.setOpts.addAll(d.builder.setOpts); 151 c.map.putAll(d.builder.map); 152 c.freeImpls.putAll(d.builder.freeImpls); 153 c.freeParams.putAll(d.builder.freeParams); 154 c.lateBindClazz.putAll(d.builder.lateBindClazz); 155 156 return c; 157 } 158 159 public final <T> ConfigurationModuleBuilder bind(Class<?> iface, Impl<?> opt) { 160 ConfigurationModuleBuilder c = deepCopy(); 161 c.processUse(opt); 162 c.freeImpls.put(iface, opt); 163 return c; 164 } 165 166 public final <T> ConfigurationModuleBuilder bindSetEntry(Class<? extends Name<Set<T>>> iface, String impl) { 167 ConfigurationModuleBuilder c = deepCopy(); 168 try { 169 c.b.bindSetEntry(iface, impl); 170 } catch (BindException e) { 171 throw new ClassHierarchyException(e); 172 } 173 return c; 174 } 175 176 public final <T> ConfigurationModuleBuilder bindSetEntry(Class<? extends Name<Set<T>>> iface, Class<? extends T> impl) { 177 ConfigurationModuleBuilder c = deepCopy(); 178 try { 179 c.b.bindSetEntry(iface, impl); 180 } catch (BindException e) { 181 throw new ClassHierarchyException(e); 182 } 183 return c; 184 } 185 186 public final <T> ConfigurationModuleBuilder bindSetEntry(Class<? extends Name<Set<T>>> iface, Impl<? extends T> opt) { 187 ConfigurationModuleBuilder c = deepCopy(); 188 c.processUse(opt); 189 c.freeImpls.put(iface, opt); 190 if (!setOpts.contains(opt)) { 191 c.setOpts.add(opt); 192 } 193 return c; 194 } 195 196 public final <T> ConfigurationModuleBuilder bindSetEntry(Class<? extends Name<Set<T>>> iface, Param<? extends T> opt) { 197 ConfigurationModuleBuilder c = deepCopy(); 198 c.processUse(opt); 199 c.freeParams.put(iface, opt); 200 if (!setOpts.contains(opt)) { 201 c.setOpts.add(opt); 202 } 203 return c; 204 } 205 206 207 public final <T> ConfigurationModuleBuilder bindImplementation(Class<T> iface, 208 Class<? extends T> impl) { 209 ConfigurationModuleBuilder c = deepCopy(); 210 try { 211 c.b.bindImplementation(iface, impl); 212 } catch (BindException e) { 213 throw new ClassHierarchyException(e); 214 } 215 return c; 216 } 217 218 public final <T> ConfigurationModuleBuilder bindImplementation(Class<T> iface, 219 String impl) { 220 ConfigurationModuleBuilder c = deepCopy(); 221 c.lateBindClazz.put(iface, impl); 222 return c; 223 } 224 225 public final <T> ConfigurationModuleBuilder bindImplementation(Class<T> iface, 226 Impl<? extends T> opt) { 227 ConfigurationModuleBuilder c = deepCopy(); 228 c.processUse(opt); 229 c.freeImpls.put(iface, opt); 230 return c; 231 } 232 233 public final <T> ConfigurationModuleBuilder bindNamedParameter( 234 Class<? extends Name<T>> name, String value) { 235 ConfigurationModuleBuilder c = deepCopy(); 236 try { 237 c.b.bindNamedParameter(name, value); 238 } catch (BindException e) { 239 throw new ClassHierarchyException(e); 240 } 241 return c; 242 } 243 244 public final <T> ConfigurationModuleBuilder bindNamedParameter( 245 Class<? extends Name<T>> name, Param<T> opt) { 246 ConfigurationModuleBuilder c = deepCopy(); 247 c.processUse(opt); 248 c.freeParams.put(name, opt); 249 return c; 250 } 251 252 public final <T> ConfigurationModuleBuilder bindNamedParameter( 253 Class<? extends Name<T>> iface, Class<? extends T> impl) { 254 ConfigurationModuleBuilder c = deepCopy(); 255 try { 256 c.b.bindNamedParameter(iface, impl); 257 } catch (BindException e) { 258 throw new ClassHierarchyException(e); 259 } 260 return c; 261 } 262 263 public final <T> ConfigurationModuleBuilder bindNamedParameter( 264 Class<? extends Name<T>> iface, Impl<? extends T> opt) { 265 ConfigurationModuleBuilder c = deepCopy(); 266 c.processUse(opt); 267 c.freeImpls.put(iface, opt); 268 return c; 269 } 270 271 public final <T> ConfigurationModuleBuilder bindConstructor(Class<T> clazz, 272 Class<? extends ExternalConstructor<? extends T>> constructor) { 273 ConfigurationModuleBuilder c = deepCopy(); 274 try { 275 c.b.bindConstructor(clazz, constructor); 276 } catch (BindException e) { 277 throw new ClassHierarchyException(e); 278 } 279 return c; 280 } 281 282 public final <T> ConfigurationModuleBuilder bindConstructor(Class<T> cons, 283 Impl<? extends ExternalConstructor<? extends T>> v) { 284 ConfigurationModuleBuilder c = deepCopy(); 285 c.processUse(v); 286 c.freeImpls.put(cons, v); 287 return c; 288 } 289 290 public final <T> ConfigurationModuleBuilder bindList(Class<? extends Name<List<T>>> iface, 291 Impl<List> opt) { 292 ConfigurationModuleBuilder c = deepCopy(); 293 c.processUse(opt); 294 c.freeImpls.put(iface, opt); 295 return c; 296 } 297 298 public final <T> ConfigurationModuleBuilder bindList(Class<? extends Name<List<T>>> iface, 299 Param<List> opt) { 300 ConfigurationModuleBuilder c = deepCopy(); 301 c.processUse(opt); 302 c.freeParams.put(iface, opt); 303 return c; 304 } 305 306 public final <T> ConfigurationModuleBuilder bindList(Class<? extends Name<List<T>>> iface, List list) { 307 ConfigurationModuleBuilder c = deepCopy(); 308 c.b.bindList(iface, list); 309 return c; 310 } 311 312 private final <T> void processUse(Object impl) { 313 Field f = map.get(impl); 314 if (f == null) { /* throw */ 315 throw new ClassHierarchyException("Unknown Impl/Param when binding " + ReflectionUtilities.getSimpleName(impl.getClass()) + ". Did you pass in a field from some other module?"); 316 } 317 if (!reqUsed.contains(f)) { 318 reqUsed.add(f); 319 } 320 if (!optUsed.contains(f)) { 321 optUsed.add(f); 322 } 323 } 324 325 public final ConfigurationModule build() throws ClassHierarchyException { 326 ConfigurationModuleBuilder c = deepCopy(); 327 328 if (!(c.reqUsed.containsAll(c.reqDecl) && c.optUsed.containsAll(c.optDecl))) { 329 Set<Field> fset = new MonotonicHashSet<>(); 330 for (Field f : c.reqDecl) { 331 if (!c.reqUsed.contains(f)) { 332 fset.add(f); 333 } 334 } 335 for (Field f : c.optDecl) { 336 if (!c.optUsed.contains(f)) { 337 fset.add(f); 338 } 339 } 340 throw new ClassHierarchyException( 341 "Found declared options that were not used in binds: " 342 + toString(fset)); 343 } 344 for (Class<?> clz : c.lateBindClazz.keySet()) { 345 try { 346 c.b.bind(ReflectionUtilities.getFullName(clz), c.lateBindClazz.get(clz)); 347 } catch (NameResolutionException e) { 348 throw new ClassHierarchyException("ConfigurationModule refers to unknown class: " + c.lateBindClazz.get(clz), e); 349 } catch (BindException e) { 350 throw new ClassHierarchyException("bind failed while initializing ConfigurationModuleBuilder", e); 351 } 352 } 353 return new ConfigurationModule(c); 354 } 355 356/* public final <T> ConfigurationModuleBuilder bind(Class<T> iface, Class<?> impl) { 357 ConfigurationModuleBuilder c = deepCopy(); 358 try { 359 c.b.bind(iface, impl); 360 } catch (BindException e) { 361 throw new ClassHierarchyException(e); 362 } 363 return c; 364 } */ 365 366 final ConfigurationModuleBuilder deepCopy() { 367 // ooh... this is a dirty trick --- we strip this's type off here, 368 // fortunately, we've all ready looked at the root object's class's 369 // fields, and we copy the information we extracted from them, so 370 // everything works out OK w.r.t. field detection. 371 return new ConfigurationModuleBuilder(this) { 372 }; 373 } 374 375 final String toString(Set<Field> s) { 376 StringBuilder sb = new StringBuilder("{"); 377 boolean first = true; 378 for (Field f : s) { 379 sb.append((first ? " " : ", ") + f.getName()); 380 first = false; 381 } 382 sb.append(" }"); 383 return sb.toString(); 384 } 385}