This project has retired. For details please refer to its Attic page.
Source code
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}