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.implementation.protobuf; 020 021import org.apache.reef.tang.ClassHierarchy; 022import org.apache.reef.tang.exceptions.NameResolutionException; 023import org.apache.reef.tang.implementation.types.*; 024import org.apache.reef.tang.proto.ClassHierarchyProto; 025import org.apache.reef.tang.types.*; 026 027import java.io.*; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.HashMap; 031import java.util.List; 032 033public class ProtocolBufferClassHierarchy implements ClassHierarchy { 034 035 private static final String regex = "[\\.\\$\\+]"; 036 private final PackageNode namespace; 037 private HashMap<String, Node> lookupTable = new HashMap<>(); 038 039 // ############## Serialize implementation ############## 040 041 // protoc doesn't believe in auto-generating constructors, so here are 042 // hand-generated ones. *sigh* 043 044 /** 045 * Deserialize a class hierarchy from a protocol buffer object. The resulting 046 * object is immutable, and does not make use of reflection to fill in any 047 * missing values. This allows it to represent non-native classes as well 048 * as snapshots of Java class hierarchies. 049 */ 050 public ProtocolBufferClassHierarchy(ClassHierarchyProto.Node root) { 051 namespace = new PackageNodeImpl(); 052 if (!root.hasPackageNode()) { 053 throw new IllegalArgumentException("Expected a package node. Got: " 054 + root); 055 } 056 // Register all the classes. 057 for (ClassHierarchyProto.Node child : root.getChildrenList()) { 058 parseSubHierarchy(namespace, child); 059 } 060 buildLookupTable(namespace); 061 // Now, register the implementations 062 for (ClassHierarchyProto.Node child : root.getChildrenList()) { 063 wireUpInheritanceRelationships(child); 064 } 065 } 066 067 private static ClassHierarchyProto.Node newClassNode(String name, 068 String fullName, boolean isInjectionCandidate, 069 boolean isExternalConstructor, boolean isUnit, 070 List<ClassHierarchyProto.ConstructorDef> injectableConstructors, 071 List<ClassHierarchyProto.ConstructorDef> otherConstructors, 072 List<String> implFullNames, Iterable<ClassHierarchyProto.Node> children) { 073 return ClassHierarchyProto.Node 074 .newBuilder() 075 .setName(name) 076 .setFullName(fullName) 077 .setClassNode( 078 ClassHierarchyProto.ClassNode.newBuilder() 079 .setIsInjectionCandidate(isInjectionCandidate) 080 .setIsExternalConstructor(isExternalConstructor) 081 .setIsUnit(isUnit) 082 .addAllInjectableConstructors(injectableConstructors) 083 .addAllOtherConstructors(otherConstructors) 084 .addAllImplFullNames(implFullNames).build()) 085 .addAllChildren(children).build(); 086 } 087 088 private static ClassHierarchyProto.Node newNamedParameterNode(String name, 089 String fullName, String simpleArgClassName, String fullArgClassName, 090 boolean isSet, 091 boolean isList, 092 String documentation, // can be null 093 String shortName, // can be null 094 String[] instanceDefault, // can be null 095 Iterable<ClassHierarchyProto.Node> children) { 096 ClassHierarchyProto.NamedParameterNode.Builder namedParameterNodeBuilder 097 = ClassHierarchyProto.NamedParameterNode.newBuilder() 098 .setSimpleArgClassName(simpleArgClassName) 099 .setFullArgClassName(fullArgClassName) 100 .setIsSet(isSet) 101 .setIsList(isList); 102 if (documentation != null) { 103 namedParameterNodeBuilder.setDocumentation(documentation); 104 } 105 if (shortName != null) { 106 namedParameterNodeBuilder.setShortName(shortName); 107 } 108 if (instanceDefault != null) { 109 namedParameterNodeBuilder.addAllInstanceDefault(Arrays.asList(instanceDefault)); 110 } 111 112 return ClassHierarchyProto.Node.newBuilder().setName(name) 113 .setFullName(fullName) 114 .setNamedParameterNode(namedParameterNodeBuilder.build()) 115 .addAllChildren(children).build(); 116 } 117 118 private static ClassHierarchyProto.Node newPackageNode(String name, 119 String fullName, Iterable<ClassHierarchyProto.Node> children) { 120 return ClassHierarchyProto.Node.newBuilder() 121 .setPackageNode(ClassHierarchyProto.PackageNode.newBuilder().build()) 122 .setName(name).setFullName(fullName).addAllChildren(children).build(); 123 } 124 125 private static ClassHierarchyProto.ConstructorDef newConstructorDef( 126 String fullClassName, List<ClassHierarchyProto.ConstructorArg> args) { 127 return ClassHierarchyProto.ConstructorDef.newBuilder() 128 .setFullClassName(fullClassName).addAllArgs(args).build(); 129 } 130 131 // these serialize...() methods copy a pieces of the class hierarchy into 132 // protobufs 133 134 private static ClassHierarchyProto.ConstructorArg newConstructorArg( 135 String fullArgClassName, String namedParameterName, boolean isFuture) { 136 ClassHierarchyProto.ConstructorArg.Builder builder = 137 ClassHierarchyProto.ConstructorArg.newBuilder() 138 .setFullArgClassName(fullArgClassName) 139 .setIsInjectionFuture(isFuture); 140 if (namedParameterName != null) { 141 builder.setNamedParameterName(namedParameterName).build(); 142 } 143 return builder.build(); 144 } 145 146 private static ClassHierarchyProto.ConstructorDef serializeConstructorDef( 147 ConstructorDef<?> def) { 148 List<ClassHierarchyProto.ConstructorArg> args = new ArrayList<>(); 149 for (ConstructorArg arg : def.getArgs()) { 150 args.add(newConstructorArg(arg.getType(), arg.getNamedParameterName(), arg.isInjectionFuture())); 151 } 152 return newConstructorDef(def.getClassName(), args); 153 } 154 155 private static ClassHierarchyProto.Node serializeNode(Node n) { 156 List<ClassHierarchyProto.Node> children = new ArrayList<>(); 157 for (Node child : n.getChildren()) { 158 children.add(serializeNode(child)); 159 } 160 if (n instanceof ClassNode) { 161 ClassNode<?> cn = (ClassNode<?>) n; 162 ConstructorDef<?>[] injectable = cn.getInjectableConstructors(); 163 ConstructorDef<?>[] all = cn.getAllConstructors(); 164 List<ConstructorDef<?>> others = new ArrayList<>(Arrays.asList(all)); 165 others.removeAll(Arrays.asList(injectable)); 166 167 List<ClassHierarchyProto.ConstructorDef> injectableConstructors = new ArrayList<>(); 168 for (ConstructorDef<?> inj : injectable) { 169 injectableConstructors.add(serializeConstructorDef(inj)); 170 } 171 List<ClassHierarchyProto.ConstructorDef> otherConstructors = new ArrayList<>(); 172 for (ConstructorDef<?> other : others) { 173 otherConstructors.add(serializeConstructorDef(other)); 174 } 175 List<String> implFullNames = new ArrayList<>(); 176 for (ClassNode<?> impl : cn.getKnownImplementations()) { 177 implFullNames.add(impl.getFullName()); 178 } 179 return newClassNode(cn.getName(), cn.getFullName(), 180 cn.isInjectionCandidate(), cn.isExternalConstructor(), cn.isUnit(), 181 injectableConstructors, otherConstructors, implFullNames, children); 182 } else if (n instanceof NamedParameterNode) { 183 NamedParameterNode<?> np = (NamedParameterNode<?>) n; 184 return newNamedParameterNode(np.getName(), np.getFullName(), 185 np.getSimpleArgName(), np.getFullArgName(), np.isSet(), np.isList(), np.getDocumentation(), 186 np.getShortName(), np.getDefaultInstanceAsStrings(), children); 187 } else if (n instanceof PackageNode) { 188 return newPackageNode(n.getName(), n.getFullName(), children); 189 } else { 190 throw new IllegalStateException("Encountered unknown type of Node: " + n); 191 } 192 } 193 194 /** 195 * Serialize a class hierarchy into a protocol buffer object. 196 * 197 * @param classHierarchy 198 * @return 199 */ 200 public static ClassHierarchyProto.Node serialize(ClassHierarchy classHierarchy) { 201 return serializeNode(classHierarchy.getNamespace()); 202 } 203 204 /** 205 * serialize a class hierarchy into a file 206 * 207 * @param file 208 * @param classHierarchy 209 * @throws IOException 210 */ 211 public static void serialize(final File file, final ClassHierarchy classHierarchy) throws IOException { 212 final ClassHierarchyProto.Node node = serializeNode(classHierarchy.getNamespace()); 213 try (final FileOutputStream output = new FileOutputStream(file)) { 214 try (final DataOutputStream dos = new DataOutputStream(output)) { 215 node.writeTo(dos); 216 } 217 } 218 } 219 220 /** 221 * Deserialize a class hierarchy from a file. The file can be generated from either Java or C# 222 * 223 * @param file 224 * @return 225 * @throws IOException 226 */ 227 public static ClassHierarchy deserialize(final File file) throws IOException { 228 try (final InputStream stream = new FileInputStream(file)) { 229 final ClassHierarchyProto.Node root = ClassHierarchyProto.Node.parseFrom(stream); 230 return new ProtocolBufferClassHierarchy(root); 231 } 232 } 233 234 private static void parseSubHierarchy(Node parent, ClassHierarchyProto.Node n) { 235 final Node parsed; 236 if (n.hasPackageNode()) { 237 parsed = new PackageNodeImpl(parent, n.getName(), n.getFullName()); 238 } else if (n.hasNamedParameterNode()) { 239 ClassHierarchyProto.NamedParameterNode np = n.getNamedParameterNode(); 240 parsed = new NamedParameterNodeImpl<Object>(parent, n.getName(), 241 n.getFullName(), np.getFullArgClassName(), np.getSimpleArgClassName(), 242 np.getIsSet(), np.getIsList(), np.getDocumentation(), np.getShortName(), 243 np.getInstanceDefaultList().toArray(new String[0])); 244 } else if (n.hasClassNode()) { 245 ClassHierarchyProto.ClassNode cn = n.getClassNode(); 246 List<ConstructorDef<?>> injectableConstructors = new ArrayList<>(); 247 List<ConstructorDef<?>> allConstructors = new ArrayList<>(); 248 249 for (ClassHierarchyProto.ConstructorDef injectable : cn 250 .getInjectableConstructorsList()) { 251 ConstructorDef<?> def = parseConstructorDef(injectable, true); 252 injectableConstructors.add(def); 253 allConstructors.add(def); 254 } 255 for (ClassHierarchyProto.ConstructorDef other : cn 256 .getOtherConstructorsList()) { 257 ConstructorDef<?> def = parseConstructorDef(other, false); 258 allConstructors.add(def); 259 260 } 261 @SuppressWarnings("unchecked") 262 ConstructorDef<Object>[] dummy = new ConstructorDef[0]; 263 parsed = new ClassNodeImpl<>(parent, n.getName(), n.getFullName(), 264 cn.getIsUnit(), cn.getIsInjectionCandidate(), 265 cn.getIsExternalConstructor(), injectableConstructors.toArray(dummy), 266 allConstructors.toArray(dummy), cn.getDefaultImplementation()); 267 } else { 268 throw new IllegalStateException("Bad protocol buffer: got abstract node" 269 + n); 270 } 271 for (ClassHierarchyProto.Node child : n.getChildrenList()) { 272 parseSubHierarchy(parsed, child); 273 } 274 } 275 276 private static ConstructorDef<?> parseConstructorDef( 277 org.apache.reef.tang.proto.ClassHierarchyProto.ConstructorDef def, 278 boolean isInjectable) { 279 List<ConstructorArg> args = new ArrayList<>(); 280 for (ClassHierarchyProto.ConstructorArg arg : def.getArgsList()) { 281 args.add(new ConstructorArgImpl(arg.getFullArgClassName(), arg 282 .getNamedParameterName(), arg.getIsInjectionFuture())); 283 } 284 return new ConstructorDefImpl<>(def.getFullClassName(), 285 args.toArray(new ConstructorArg[0]), isInjectable); 286 } 287 288 private static String getNthPrefix(String str, int n) { 289 n++; // want this function to be zero indexed... 290 for (int i = 0; i < str.length(); i++) { 291 char c = str.charAt(i); 292 if (c == '.' || c == '$' || c == '+') { 293 n--; 294 } 295 if (n == 0) { 296 return str.substring(0, i); 297 } 298 } 299 if (n == 1) { 300 return str; 301 } else { 302 throw new ArrayIndexOutOfBoundsException(); 303 } 304 } 305 306 private void buildLookupTable(Node n) { 307 for (Node child : n.getChildren()) { 308 lookupTable.put(child.getFullName(), child); 309 buildLookupTable(child); 310 } 311 } 312 313 @SuppressWarnings({"rawtypes", "unchecked"}) 314 private void wireUpInheritanceRelationships(final ClassHierarchyProto.Node n) { 315 if (n.hasClassNode()) { 316 final ClassHierarchyProto.ClassNode cn = n.getClassNode(); 317 final ClassNode iface; 318 try { 319 iface = (ClassNode) getNode(n.getFullName()); 320 } catch (NameResolutionException e) { 321 throw new IllegalStateException("When reading protocol buffer node " 322 + n.getFullName() + " does not exist. Full record is " + n, e); 323 } 324 for (String impl : cn.getImplFullNamesList()) { 325 try { 326 iface.putImpl((ClassNode) getNode(impl)); 327 } catch (NameResolutionException e) { 328 throw new IllegalStateException("When reading protocol buffer node " 329 + n + " refers to non-existent implementation:" + impl); 330 } catch (ClassCastException e) { 331 try { 332 throw new IllegalStateException( 333 "When reading protocol buffer node " + n 334 + " found implementation" + getNode(impl) 335 + " which is not a ClassNode!"); 336 } catch (NameResolutionException e2) { 337 throw new IllegalStateException( 338 "Got 'cant happen' exception when producing error message for " 339 + e); 340 } 341 } 342 } 343 } 344 345 for (ClassHierarchyProto.Node child : n.getChildrenList()) { 346 wireUpInheritanceRelationships(child); 347 } 348 } 349 350 @Override 351 public Node getNode(String fullName) throws NameResolutionException { 352 353 Node ret = lookupTable.get(fullName); 354/* String[] tok = fullName.split(regex); 355 356 Node ret = namespace.get(fullName); 357 for (int i = 0; i < tok.length; i++) { 358 Node n = namespace.get(getNthPrefix(fullName, i)); 359 if (n != null) { 360 for (i++; i < tok.length; i++) { 361 n = n.get(tok[i]); 362 if (n == null) { 363 throw new NameResolutionException(fullName, getNthPrefix(fullName, 364 i - 1)); 365 } 366 } 367 return n; 368 } 369 } */ 370 if (ret != null) { 371 return ret; 372 } else { 373 throw new NameResolutionException(fullName, ""); 374 } 375 } 376 377 @Override 378 public boolean isImplementation(ClassNode<?> inter, ClassNode<?> impl) { 379 return impl.isImplementationOf(inter); 380 } 381 382 @Override 383 public ClassHierarchy merge(ClassHierarchy ch) { 384 if (this == ch) { 385 return this; 386 } 387 throw new UnsupportedOperationException( 388 "Cannot merge ExternalClassHierarchies yet!"); 389 } 390 391 @Override 392 public Node getNamespace() { 393 return namespace; 394 } 395 396}